From 986d4f23633a0359268e80050f091571a3d10b88 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Feb 2026 21:54:03 +0000 Subject: [PATCH 1/5] feat: Add authless mode for Claude.ai custom connectors Claude.ai custom connectors only support authless or OAuth 2.1 connections, not Bearer token authentication via headers. This change adds an ALLOW_AUTHLESS environment variable (default: "true") that bypasses authentication for SSE transport endpoints (/sse, /sse/, /message), enabling Claude.ai to connect while keeping Bearer token auth required for the /mcp JSON-RPC endpoint for backward compatibility. Changes: - src/config/env.ts: Add ALLOW_AUTHLESS to environment schema - src/lambda.ts: Add authless mode logic to auth middleware - src/index.ts: Apply same authless mode logic to local dev server - template.yaml: Add AllowAuthless CloudFormation parameter - README.md: Add Claude.ai custom connector setup section - docs/authentication.md: Document authless mode configuration - AGENTS.md: Update authentication flow documentation https://claude.ai/code/session_019Wnbwj3oTzyM3sfGUh8Xr8 --- AGENTS.md | 22 +++++++++- README.md | 26 ++++++++++++ docs/authentication.md | 94 +++++++++++++++++++++++++++++++++++++++++- src/config/env.ts | 4 ++ src/index.ts | 27 +++++++----- src/lambda.ts | 33 +++++++++------ template.yaml | 9 ++++ 7 files changed, 189 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 032fef5..5e9a5ce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -288,20 +288,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ ### Authentication Flow -**Two Types of Auth**: +**Three Types of Auth**: 1. **Bearer Token** (AUTH_TOKEN): - Static token for Lambda authentication - Generated during deployment - Validates client requests to Lambda function - Stored in environment variable + - Required for `/mcp` JSON-RPC endpoint -2. **OAuth Tokens** (Strava): +2. **Authless Mode** (ALLOW_AUTHLESS): + - When `ALLOW_AUTHLESS=true` (default), SSE endpoints bypass auth + - Enables Claude.ai custom connectors (which don't support custom headers) + - Applies to: `/sse`, `/sse/`, `/message` endpoints + - Does NOT apply to: `/mcp` endpoint (still requires Bearer token) + - Security: Keep Lambda URL private when enabled + +3. **OAuth Tokens** (Strava): - Access token (short-lived, ~6 hours) - Refresh token (long-lived) - Automatically managed by StravaClient - Stored in environment variables (CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN) +**Authentication Middleware Logic**: +```typescript +// Simplified auth flow +if (path === '/health') → bypass auth +if (ALLOW_AUTHLESS && (path === '/sse' || path === '/message')) → bypass auth +if (path === '/message' && valid session) → bypass auth +else → require Bearer token +``` + ### Local Development Workflow ```bash @@ -539,6 +556,7 @@ sam delete --stack-name strava-mcp-stack # Delete deployment - `STRAVA_CLIENT_SECRET` - Strava API client secret - `STRAVA_REFRESH_TOKEN` - Strava OAuth refresh token - `AUTH_TOKEN` - Bearer token for Lambda authentication +- `ALLOW_AUTHLESS` - When "true", SSE endpoints bypass auth (default: "true") - `NODE_ENV` - Environment (production/development) ### File Locations diff --git a/README.md b/README.md index a6f09dc..ec20015 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,32 @@ Need to see the config again? Run: bun run deploy:show-config ``` +### 5. Claude.ai Custom Connectors (Web/Mobile) + +Claude.ai custom connectors use **authless mode** via SSE transport. This is enabled by default (`ALLOW_AUTHLESS=true`). + +**Setup Steps:** +1. Go to **Claude.ai** → **Settings** → **Connectors** → **Add custom connector** +2. Enter just the **base URL** (no path): `https://your-function-url.lambda-url.us-east-1.on.aws` +3. Claude.ai will automatically discover the `/sse` endpoint and connect +4. Verify tools appear and can be invoked + +**Important Notes:** +- Claude.ai does NOT support custom headers (no Bearer token auth) +- Authless mode only applies to SSE endpoints (`/sse`, `/message`) +- The `/mcp` JSON-RPC endpoint still requires Bearer token authentication +- Keep your Lambda Function URL private for security + +**Security Considerations:** +- Anyone with your Lambda URL can access your Strava data in authless mode +- This is acceptable for personal use where the URL is not shared +- For production, consider adding IP allowlisting via AWS WAF + +To disable authless mode (require Bearer tokens everywhere): +```bash +sam deploy --parameter-overrides AllowAuthless=false +``` + ## Documentation 📚 **[Full Documentation](https://stealinglight.github.io/StravaMCP)** diff --git a/docs/authentication.md b/docs/authentication.md index f57a3eb..712b28d 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -11,7 +11,13 @@ The Lambda function uses **Bearer token authentication** to secure access to you - ✅ Works with Claude web, mobile, and desktop - ✅ Easy to rotate tokens when needed -## How It Works +## Authentication Modes + +The server supports two authentication modes: + +### 1. Bearer Token Authentication (Default for `/mcp` endpoint) + +For Claude Desktop and other MCP clients that support custom headers. ``` ┌──────────────┐ @@ -31,6 +37,33 @@ The Lambda function uses **Bearer token authentication** to secure access to you └──────────────┘ ``` +### 2. Authless Mode (For Claude.ai Custom Connectors) + +Claude.ai custom connectors only support authless or OAuth 2.1 (DCR) connections. Since OAuth 2.1 with DCR is complex to implement, this server provides an **authless mode** for SSE endpoints. + +When `ALLOW_AUTHLESS=true` (default), the SSE transport endpoints (`/sse`, `/sse/`, `/message`) bypass authentication, allowing Claude.ai to connect without Bearer tokens. + +``` +┌──────────────┐ +│ Claude.ai │ +│ (Web/Mobile) │ +└──────┬───────┘ + │ SSE connection (no auth required) + ▼ +┌──────────────┐ +│ Lambda │─── ALLOW_AUTHLESS=true +│ (Middleware)│ Skips auth for SSE +└──────┬───────┘ + │ Direct access + ▼ +┌──────────────┐ +│ MCP Server │ +│ (Strava API) │ +└──────────────┘ +``` + +**Important**: The `/mcp` JSON-RPC endpoint still requires Bearer token authentication for backward compatibility with other clients. + ## Setup Steps ### 1. Deploy and Auto-Generate AUTH_TOKEN @@ -116,6 +149,20 @@ Add as a Custom Connector in Claude Settings: **Important**: The URL must end with `/mcp` - this is the MCP endpoint. +#### Claude.ai Custom Connectors (Authless Mode) + +For Claude.ai web and mobile, use the authless SSE transport: + +1. Go to **Claude.ai** → **Settings** → **Connectors** → **Add custom connector** +2. Enter just the **base URL** (no `/mcp` or `/sse` path): + ``` + https://your-function-url.lambda-url.us-east-1.on.aws + ``` +3. Claude.ai will automatically discover the `/sse` endpoint +4. Verify tools appear in the conversation + +**Note**: Authless mode is enabled by default (`ALLOW_AUTHLESS=true`). Claude.ai cannot send custom headers, so Bearer token auth is not supported for Claude.ai connectors. + ### 4. Test Authentication Test that authentication is working: @@ -171,6 +218,51 @@ To rotate your authentication token: - Use short or predictable tokens - Reuse tokens across multiple services +## Authless Mode Configuration + +### Enabling Authless Mode (Default) + +Authless mode is **enabled by default** for Claude.ai compatibility. The `ALLOW_AUTHLESS` parameter is set to `"true"` in the SAM template. + +When enabled: +- SSE endpoints (`/sse`, `/sse/`, `/message`) do not require authentication +- The `/mcp` JSON-RPC endpoint still requires Bearer token authentication +- Claude.ai custom connectors can connect using the base URL + +### Disabling Authless Mode + +To require Bearer token authentication for all endpoints: + +```bash +# During deployment +sam deploy --parameter-overrides AllowAuthless=false + +# Or update samconfig.toml +[default.deploy.parameters] +parameter_overrides = [ + "AllowAuthless=false", + # ... other parameters +] +``` + +When disabled: +- All endpoints require Bearer token authentication +- Claude.ai custom connectors will NOT work (they cannot send custom headers) +- Only Claude Desktop and clients supporting custom headers can connect + +### Security Implications + +**Authless mode enabled (`ALLOW_AUTHLESS=true`):** +- ⚠️ Anyone with your Lambda URL can access your Strava data via SSE +- ✅ Keep your Lambda Function URL private +- ✅ Acceptable for personal use where URL is not shared +- 💡 Consider AWS WAF for IP allowlisting in production + +**Authless mode disabled (`ALLOW_AUTHLESS=false`):** +- ✅ All requests require valid Bearer token +- ❌ Claude.ai custom connectors won't work +- ✅ Maximum security for shared environments + ## Security Considerations ### What This Protects diff --git a/src/config/env.ts b/src/config/env.ts index 0cc7bab..84e4d5b 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -12,6 +12,9 @@ const envSchema = z.object({ STRAVA_REFRESH_TOKEN: z.string().min(1, 'STRAVA_REFRESH_TOKEN is required'), AUTH_TOKEN: z.string().min(32, 'AUTH_TOKEN must be at least 32 characters'), PORT: z.string().default('3000').transform(Number), + // ALLOW_AUTHLESS: When "true", bypasses auth for SSE endpoints (/sse, /sse/, /message) + // This enables Claude.ai custom connectors which don't support Bearer token auth + ALLOW_AUTHLESS: z.string().default('false').transform((val) => val.toLowerCase() === 'true'), }); /** @@ -25,6 +28,7 @@ export function getConfig() { STRAVA_REFRESH_TOKEN: process.env.STRAVA_REFRESH_TOKEN, AUTH_TOKEN: process.env.AUTH_TOKEN, PORT: process.env.PORT, + ALLOW_AUTHLESS: process.env.ALLOW_AUTHLESS, }); if (!result.success) { diff --git a/src/index.ts b/src/index.ts index 16bf1f2..1f62d24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -325,20 +325,27 @@ async function main() { // Authentication middleware for local dev (optional - can be disabled) // Supports both Authorization header and query parameter + // When ALLOW_AUTHLESS=true, SSE endpoints bypass auth for Claude.ai custom connectors app.use((req: Request, res: Response, next) => { if (req.path === '/health') { return next(); } - // For SSE endpoint, authentication happens via query param or header - if (req.path === '/sse' || req.path === '/sse/') { - // SSE authentication happens here before establishing connection - // Continue to token validation below + // Check if authless mode is enabled for SSE endpoints + // This allows Claude.ai custom connectors to connect without Bearer tokens + const isSSEEndpoint = req.path === '/sse' || req.path === '/sse/'; + const isMessageEndpoint = req.path === '/message'; + + if (config.ALLOW_AUTHLESS && (isSSEEndpoint || isMessageEndpoint)) { + if (isSSEEndpoint) { + console.error('[StravaServer] Authless SSE connection allowed (ALLOW_AUTHLESS=true)'); + } + return next(); } - // For SSE message endpoint, trust valid session IDs + // For SSE message endpoint with valid session, trust the session const sessionId = req.query.sessionId as string; - if (req.path === '/message' && sessionId && transports[sessionId]) { + if (isMessageEndpoint && sessionId && transports[sessionId]) { return next(); } @@ -346,23 +353,23 @@ async function main() { // If AUTH_TOKEN is set in .env, validate it if (config.AUTH_TOKEN && config.AUTH_TOKEN.length > 0) { let token: string | undefined; - + const authHeader = req.headers['authorization']; if (authHeader && authHeader.startsWith('Bearer ')) { token = authHeader.substring(7); } else if (req.query.token) { token = req.query.token as string; } - + if (!token || token !== config.AUTH_TOKEN) { console.error('[StravaServer] Invalid or missing token'); - return res.status(401).json({ + return res.status(401).json({ error: 'Unauthorized', message: 'Invalid or missing token' }); } } - + next(); }); diff --git a/src/lambda.ts b/src/lambda.ts index faf4a88..65e6705 100644 --- a/src/lambda.ts +++ b/src/lambda.ts @@ -91,25 +91,37 @@ function initializeServer(): express.Application { // Authentication middleware - verify Bearer token // Skip auth for health check endpoint // Supports both Authorization header and query parameter for Claude connector compatibility + // When ALLOW_AUTHLESS=true, SSE endpoints bypass auth for Claude.ai custom connectors app.use((req: Request, res: Response, next) => { if (req.path === '/health') { return next(); } - // For SSE endpoint, authentication happens via query param or header - if (req.path === '/sse' || req.path === '/sse/') { - // SSE authentication happens here before establishing connection - // Continue to token validation below + if (!config) { + // Initialize config if not already done (shouldn't happen, but safety check) + config = getConfig(); } - // For SSE message endpoint, trust valid session IDs + // Check if authless mode is enabled for SSE endpoints + // This allows Claude.ai custom connectors to connect without Bearer tokens + const isSSEEndpoint = req.path === '/sse' || req.path === '/sse/'; + const isMessageEndpoint = req.path === '/message'; + + if (config.ALLOW_AUTHLESS && (isSSEEndpoint || isMessageEndpoint)) { + if (isSSEEndpoint) { + console.error('[StravaLambda] Authless SSE connection allowed (ALLOW_AUTHLESS=true)'); + } + return next(); + } + + // For SSE message endpoint with valid session, trust the session const sessionId = req.query.sessionId as string; - if (req.path === '/message' && sessionId && transports[sessionId]) { + if (isMessageEndpoint && sessionId && transports[sessionId]) { return next(); } let token: string | undefined; - + // Try Authorization header first (preferred for API clients) const authHeader = req.headers['authorization']; if (authHeader && authHeader.startsWith('Bearer ')) { @@ -119,15 +131,10 @@ function initializeServer(): express.Application { else if (req.query.token) { token = req.query.token as string; } - - if (!config) { - // Initialize config if not already done (shouldn't happen, but safety check) - config = getConfig(); - } if (!token || token !== config.AUTH_TOKEN) { console.error('[StravaLambda] Invalid or missing token'); - return res.status(401).json({ + return res.status(401).json({ error: 'Unauthorized', message: 'Invalid or missing token. Use: Authorization: Bearer OR ?token=' }); diff --git a/template.yaml b/template.yaml index e944fb6..991e970 100644 --- a/template.yaml +++ b/template.yaml @@ -39,6 +39,14 @@ Parameters: NoEcho: true MinLength: 32 + AllowAuthless: + Type: String + Description: When "true", allows SSE connections without authentication (for Claude.ai custom connectors) + Default: "true" + AllowedValues: + - "true" + - "false" + Resources: StravaMCPFunction: Type: AWS::Serverless::Function @@ -53,6 +61,7 @@ Resources: STRAVA_CLIENT_SECRET: !Ref StravaClientSecret STRAVA_REFRESH_TOKEN: !Ref StravaRefreshToken AUTH_TOKEN: !Ref AuthToken + ALLOW_AUTHLESS: !Ref AllowAuthless FunctionUrlConfig: AuthType: NONE # Public URL with Bearer token authentication in application code Cors: From 931922065c39f92ec5e5b7912eda835812574e0d Mon Sep 17 00:00:00 2001 From: Stealinglight <120747552+Stealinglight@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:44:03 -0800 Subject: [PATCH 2/5] Update docs/authentication.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/authentication.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/authentication.md b/docs/authentication.md index 712b28d..bf6bf92 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -252,16 +252,16 @@ When disabled: ### Security Implications -**Authless mode enabled (`ALLOW_AUTHLESS=true`):** -- ⚠️ Anyone with your Lambda URL can access your Strava data via SSE -- ✅ Keep your Lambda Function URL private -- ✅ Acceptable for personal use where URL is not shared -- 💡 Consider AWS WAF for IP allowlisting in production - -**Authless mode disabled (`ALLOW_AUTHLESS=false`):** -- ✅ All requests require valid Bearer token +**Authless mode enabled (`ALLOW_AUTHLESS=true`, advanced / opt-in):** +- ⚠️ Not enabled by default. Enabling this on a publicly accessible Lambda URL exposes `/sse`, `/sse/`, and `/message` without authentication. +- ⚠️ Anyone who learns your Lambda URL (via logs, screenshots, or misconfiguration) can access your Strava-backed tools via SSE; URL secrecy alone is **not** sufficient protection. +- ✅ Only enable for strictly local or personal use where you fully control access to the URL. +- 💡 If you must use authless mode in a cloud environment, combine it with additional controls such as AWS WAF/IP allowlisting and/or an extra shared secret (e.g., a query parameter or header checked by your Lambda). + +**Authless mode disabled (`ALLOW_AUTHLESS=false`, recommended default):** +- ✅ All requests require a valid Bearer token - ❌ Claude.ai custom connectors won't work -- ✅ Maximum security for shared environments +- ✅ Maximum security for shared, team, or production environments ## Security Considerations From ca0915f3fe2b00e4515560e3fdba156888ef982f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Feb 2026 01:41:09 +0000 Subject: [PATCH 3/5] feat: Verify and enhance Lambda SSE configuration - Update Lambda timeout to 900s (max for Function URLs) for long SSE sessions - Improve CORS config with explicit methods/headers for Claude.ai compatibility - Add /debug endpoint for troubleshooting Claude.ai connection issues - Update /health endpoint with diagnostic info (authless mode, timestamp) - Sync version numbers to 3.0.0 across all files - Document serverless-express streaming limitation (GitHub issue #655) - Add known_limitations to /debug endpoint response Note: serverless-express does not support Lambda response streaming. If SSE fails, consider AWS Lambda Web Adapter as an alternative. https://claude.ai/code/session_019Wnbwj3oTzyM3sfGUh8Xr8 --- src/index.ts | 47 +++++++++++++++++++++++++++++++++---- src/lambda.ts | 64 ++++++++++++++++++++++++++++++++++++++++++++++----- template.yaml | 15 ++++++++---- 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1f62d24..7d862ae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -92,7 +92,7 @@ try { const server = new Server( { name: 'strava-mcp-server', - version: '1.0.0', + version: '3.0.0', }, { capabilities: { @@ -327,7 +327,7 @@ async function main() { // Supports both Authorization header and query parameter // When ALLOW_AUTHLESS=true, SSE endpoints bypass auth for Claude.ai custom connectors app.use((req: Request, res: Response, next) => { - if (req.path === '/health') { + if (req.path === '/health' || req.path === '/debug') { return next(); } @@ -373,9 +373,43 @@ async function main() { next(); }); - // Health check endpoint + // Health check endpoint - enhanced with diagnostic info app.get('/health', (_req: Request, res: Response) => { - res.json({ status: 'healthy', version: '2.0.0' }); + res.json({ + status: 'healthy', + version: '3.0.0', + runtime: 'local', + authless: config?.ALLOW_AUTHLESS ?? false, + timestamp: new Date().toISOString(), + }); + }); + + // Debug endpoint - helps troubleshoot Claude.ai connection issues + app.get('/debug', (_req: Request, res: Response) => { + res.json({ + status: 'ok', + version: '3.0.0', + authless_enabled: config?.ALLOW_AUTHLESS ?? false, + environment: process.env.NODE_ENV || 'development', + endpoints: { + health: '/health', + debug: '/debug', + sse: '/sse (GET, establishes SSE connection)', + message: '/message (POST, requires sessionId query param)', + mcp: '/mcp (POST, requires Bearer token)', + }, + claude_ai_setup: { + connector_url: `Use base URL only: http://localhost:${config.PORT}`, + auth_mode: config?.ALLOW_AUTHLESS ? 'Authless (SSE endpoints bypass auth)' : 'Bearer token required', + transport: 'SSE', + note: 'For local testing with Claude.ai, use ngrok or similar to expose localhost', + }, + sse_headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }, + }); }); // SSE endpoint - establishes the server-to-client event stream @@ -460,7 +494,7 @@ async function main() { }, serverInfo: { name: 'strava-mcp-server', - version: '2.0.0', + version: '3.0.0', }, }; break; @@ -681,7 +715,10 @@ async function main() { app.listen(port, () => { console.error(`[StravaServer] Remote MCP server running on http://localhost:${port}`); console.error(`[StravaServer] MCP endpoint: http://localhost:${port}/mcp`); + console.error(`[StravaServer] SSE endpoint: http://localhost:${port}/sse`); console.error(`[StravaServer] Health check: http://localhost:${port}/health`); + console.error(`[StravaServer] Debug info: http://localhost:${port}/debug`); + console.error(`[StravaServer] Authless mode: ${config.ALLOW_AUTHLESS ? 'ENABLED' : 'DISABLED'}`); }); } diff --git a/src/lambda.ts b/src/lambda.ts index 65e6705..0f6f4b0 100644 --- a/src/lambda.ts +++ b/src/lambda.ts @@ -73,6 +73,19 @@ import { * * Wraps the Express-based MCP server for AWS Lambda using serverless-express. * Implements JSON-RPC over HTTP transport for Claude connector compatibility. + * + * SSE STREAMING NOTE: + * The @codegenie/serverless-express library does NOT support Lambda response streaming + * (see https://github.com/CodeGenieApp/serverless-express/issues/655). + * While the Lambda Function URL is configured with InvokeMode: RESPONSE_STREAM, + * the serverless-express wrapper buffers responses before sending. + * + * CURRENT WORKAROUND: The /mcp JSON-RPC endpoint works reliably for all clients. + * Claude.ai should connect to the base URL and use SSE, but if SSE doesn't work, + * alternative approaches include: + * 1. AWS Lambda Web Adapter (https://github.com/awslabs/aws-lambda-web-adapter) + * 2. Direct Lambda streaming without Express wrapper for SSE endpoints + * 3. Using awslambda.streamifyResponse() for native streaming support */ // Initialize configuration @@ -89,11 +102,11 @@ function initializeServer(): express.Application { const transports: Record = {}; // Authentication middleware - verify Bearer token - // Skip auth for health check endpoint + // Skip auth for health check and debug endpoints // Supports both Authorization header and query parameter for Claude connector compatibility // When ALLOW_AUTHLESS=true, SSE endpoints bypass auth for Claude.ai custom connectors app.use((req: Request, res: Response, next) => { - if (req.path === '/health') { + if (req.path === '/health' || req.path === '/debug') { return next(); } @@ -161,7 +174,7 @@ function initializeServer(): express.Application { mcpServer = new Server( { name: 'strava-mcp-server', - version: '2.0.0', + version: '3.0.0', }, { capabilities: { @@ -384,9 +397,48 @@ function initializeServer(): express.Application { } }); - // Health check endpoint + // Health check endpoint - enhanced with diagnostic info app.get('/health', (_req: Request, res: Response) => { - res.json({ status: 'healthy', version: '2.0.0', environment: 'lambda' }); + res.json({ + status: 'healthy', + version: '3.0.0', + runtime: 'lambda', + authless: config?.ALLOW_AUTHLESS ?? false, + timestamp: new Date().toISOString(), + }); + }); + + // Debug endpoint - helps troubleshoot Claude.ai connection issues + app.get('/debug', (_req: Request, res: Response) => { + res.json({ + status: 'ok', + version: '3.0.0', + authless_enabled: config?.ALLOW_AUTHLESS ?? false, + environment: process.env.NODE_ENV || 'development', + endpoints: { + health: '/health', + debug: '/debug', + sse: '/sse (GET, establishes SSE connection)', + message: '/message (POST, requires sessionId query param)', + mcp: '/mcp (POST, requires Bearer token)', + }, + claude_ai_setup: { + connector_url: 'Use base URL only, no path (e.g., https://xyz.lambda-url.us-east-1.on.aws)', + auth_mode: config?.ALLOW_AUTHLESS ? 'Authless (SSE endpoints bypass auth)' : 'Bearer token required', + transport: 'SSE', + note: 'Claude.ai will auto-discover /sse endpoint from base URL', + }, + sse_headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }, + known_limitations: { + sse_streaming: 'serverless-express does not support Lambda response streaming (see GitHub issue #655)', + workaround: 'If SSE fails, consider AWS Lambda Web Adapter or direct Lambda streaming', + mcp_endpoint: '/mcp JSON-RPC endpoint works reliably for all clients', + }, + }); }); // SSE endpoint - establishes the server-to-client event stream @@ -471,7 +523,7 @@ function initializeServer(): express.Application { }, serverInfo: { name: 'strava-mcp-server', - version: '2.0.0', + version: '3.0.0', }, }; break; diff --git a/template.yaml b/template.yaml index 991e970..fc3a0b2 100644 --- a/template.yaml +++ b/template.yaml @@ -8,7 +8,7 @@ Description: > Globals: Function: - Timeout: 300 # 5 minutes (Lambda max for Function URLs) + Timeout: 900 # 15 minutes (max for Lambda Function URLs) - needed for long SSE connections MemorySize: 512 # MB (Free tier allows up to 3.2M seconds of 512MB compute) Runtime: nodejs20.x Architectures: @@ -66,11 +66,18 @@ Resources: AuthType: NONE # Public URL with Bearer token authentication in application code Cors: AllowOrigins: - - '*' + - '*' # Allow all origins for flexibility (Claude.ai, Claude Desktop, etc.) AllowMethods: - - '*' + - GET + - POST + - OPTIONS AllowHeaders: - - '*' + - Content-Type + - Authorization + - Accept + - X-Requested-With + AllowCredentials: false + MaxAge: 86400 # Cache preflight for 24 hours InvokeMode: RESPONSE_STREAM # Enable streaming for SSE support Tags: Project: StravaMCP From 3bebb84685736b370d244c4d7a29837123bacfb0 Mon Sep 17 00:00:00 2001 From: Stealinglight <120747552+Stealinglight@users.noreply.github.com> Date: Wed, 4 Feb 2026 22:49:49 -0800 Subject: [PATCH 4/5] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ec20015..b3426b9 100644 --- a/README.md +++ b/README.md @@ -151,11 +151,11 @@ Claude.ai custom connectors use **authless mode** via SSE transport. This is ena - Keep your Lambda Function URL private for security **Security Considerations:** -- Anyone with your Lambda URL can access your Strava data in authless mode -- This is acceptable for personal use where the URL is not shared -- For production, consider adding IP allowlisting via AWS WAF +- Authless mode is **high risk** and should be treated as **personal-use-only** +- If authless mode is enabled, anyone with your Lambda URL can access and act on your Strava data +- Authless mode is **not recommended for production**; if you must use it, combine it with stricter controls such as AWS WAF IP allowlisting or an additional shared secret -To disable authless mode (require Bearer tokens everywhere): +**Recommended secure deployment (authless disabled, Bearer tokens required everywhere):** ```bash sam deploy --parameter-overrides AllowAuthless=false ``` From f51d43a58ab1810a339bb1c3da1b6851d9a30cd7 Mon Sep 17 00:00:00 2001 From: Stealinglight <120747552+Stealinglight@users.noreply.github.com> Date: Wed, 4 Feb 2026 22:50:14 -0800 Subject: [PATCH 5/5] Update src/config/env.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/config/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/env.ts b/src/config/env.ts index 84e4d5b..a5d227f 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -14,7 +14,7 @@ const envSchema = z.object({ PORT: z.string().default('3000').transform(Number), // ALLOW_AUTHLESS: When "true", bypasses auth for SSE endpoints (/sse, /sse/, /message) // This enables Claude.ai custom connectors which don't support Bearer token auth - ALLOW_AUTHLESS: z.string().default('false').transform((val) => val.toLowerCase() === 'true'), + ALLOW_AUTHLESS: z.string().default('true').transform((val) => val.toLowerCase() === 'true'), }); /**