<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Anna's AI lab]]></title><description><![CDATA[Anna's AI lab]]></description><link>https://annaszoboszlai.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 05 Sep 2026 14:14:45 GMT</lastBuildDate><atom:link href="https://annaszoboszlai.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a Strava MCP Server: Running Data Meets AI]]></title><description><![CDATA[I've been using Strava for the past couple of years to track my runs. Like most runners, I'm curious about my progress: Am I getting faster? How does this month compare to last? What was my longest ru]]></description><link>https://annaszoboszlai.hashnode.dev/building-a-strava-mcp-server-running-data-meets-ai</link><guid isPermaLink="true">https://annaszoboszlai.hashnode.dev/building-a-strava-mcp-server-running-data-meets-ai</guid><category><![CDATA[mcp]]></category><category><![CDATA[strava]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[szoboszlaianna]]></dc:creator><pubDate>Sun, 15 Mar 2026 18:49:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69b6d162f4eb2f8b04a16867/634850ff-f12f-4506-8ae1-5bda050d6834.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been using Strava for the past couple of years to track my runs. Like most runners, I'm curious about my progress: Am I getting faster? How does this month compare to last? What was my longest run?</p>
<p>Then, a few weeks ago, I discovered that Strava has a well-documented <a href="https://developers.strava.com/">API</a> that lets you programmatically access all your data and activities.</p>
<p>Around the same time, I had started using Claude Desktop more and more for personal productivity. For example, checking my emails using the Gmail connector, asking it to suggest what I should unsubscribe from. The experience of having Claude help manage my inbox was eye-opening. Instead of manually sifting through hundreds of emails, I could just ask: "What newsletters am I subscribed to that I haven't opened in months?"</p>
<p>That's when the idea hit me: What if I could do the same thing with my Strava data? What if I could ask Claude about my runs and progress, and have it analyze my training patterns just like it helps me manage my inbox?</p>
<p>So I decided to build a Strava connector for Claude Desktop. Here's how it went, what I learned, and how you can use it too.</p>
<h2>The Spark: Bringing AI to Running Data</h2>
<p>The power of Claude Desktop's MCP (Model Context Protocol) connectors is that they turn AI into a personalized assistant that actually knows your data. With the Gmail connector, Claude doesn't just know <em>about</em> email; it knows <em>my</em> emails. It can give me specific, actionable insights.</p>
<p>I wanted that same experience for my running data. I wanted to simply ask: "How's my running been this month compared to last?" and get an intelligent analysis.</p>
<h2>What is MCP (Model Context Protocol)?</h2>
<p>The <a href="https://www.anthropic.com/news/model-context-protocol">Model Context Protocol</a> is a standard created by Anthropic that allows AI assistants like Claude to connect to external data sources and tools. Think of it as a bridge between Claude and the rest of your digital world.</p>
<p>With MCP, you can:</p>
<ul>
<li><p>Give Claude access to your data (like Strava activities, Google Drive files, or databases)</p>
</li>
<li><p>Let Claude perform actions on your behalf (create tasks, send emails, etc.)</p>
</li>
<li><p>Build custom integrations</p>
</li>
</ul>
<h2>The Technical Architecture</h2>
<p>My Strava MCP server is built with Node.js and TypeScript, implementing the MCP specification to provide five main tools:</p>
<p>1. <strong>get_recent_activities</strong> - Your activity feed</p>
<pre><code class="language-plaintext">// Example: Get your 10 most recent runs
{
  "limit": 10,
  "activity_type": "Run"
}
</code></pre>
<p>2. <strong>get_activity_details</strong>- Deep dive into a specific workout</p>
<pre><code class="language-typescript">// Get detailed stats for a specific activity
{
  "activity_id": "123456789"
}
</code></pre>
<p>3. <strong>get_athlete_profile</strong>- Your basic profile info</p>
<pre><code class="language-typescript">// Get profile information, stats, and gear
{}
</code></pre>
<p>4. <strong>get_monthly_statat</strong>-Aggregate monthly performance</p>
<pre><code class="language-typescript">// Get comprehensive stats for a specific month
{
  "year": 2025,
  "month": 12
}
</code></pre>
<p>5. <strong>get_activites_by_date_range</strong>- custom time periods</p>
<pre><code class="language-typescript">// Query activities within a specific date range
{
  "start_date": "2026-01-01",
  "end_date": "2026-01-31"
}
</code></pre>
<p>All data is returned in <strong>metric units</strong> (km, m, km/h, min/km pace) because, well, I'm European and that's what makes sense to me!</p>
<h2>The OAuth Challenge (And How I Solved It)</h2>
<p>The biggest technical hurdle wasn't the MCP implementation. It was handling Strava's OAuth authentication flow properly. Strava's API tokens expire after 6 hours and require periodic refresh.</p>
<p><strong>The limitation</strong>: Unlike first-party connectors like Gmail, custom MCP servers in Claude Desktop can't use popup authentication flows. There's no "Connect to Strava" button like you'd see with official integrations (or at least I havn't found a way for now).</p>
<p><strong>My solution</strong>: A one-time setup script that gets your initial tokens, then automatic refresh handling after that.</p>
<p>On first run, you execute a simple OAuth flow script that gets your tokens. After that initial setup, the server handles everything automatically. It checks if the token expiration is expired and refreshed it if needed. Once it's running, you never think about authentication again.</p>
<pre><code class="language-typescript">async function ensureValidToken(): Promise&lt;string&gt; {
  const tokens = loadTokens();
  
  // Check if token is expired or about to expire (within 5 minutes)
  const expiresAt = tokens.expires_at || 0;
  const now = Math.floor(Date.now() / 1000);
  
  if (expiresAt - now &lt; 300) {
    // Token expired or expiring soon - refresh it
    const newTokens = await refreshAccessToken(tokens.refresh_token);
    saveTokens(newTokens);
    return newTokens.access_token;
  }
  
  return tokens.access_token;
}
</code></pre>
<p>The key: proactive refresh (5-minute buffer before expiration) and persistent storage so tokens survive server restarts.</p>
<h2>Real-World Usage Examples</h2>
<p><strong>Quick check-ins:</strong></p>
<ul>
<li><p>"How was my latest run?"</p>
</li>
<li><p>"What activities did I do this week?"</p>
</li>
</ul>
<p><strong>Performance analysis:</strong></p>
<ul>
<li><p>"What's my average pace this month compared to last month?"</p>
</li>
<li><p>"How many kilometers have I run this week?"</p>
</li>
</ul>
<p><strong>Trend spotting:</strong></p>
<ul>
<li><p>"Give me stats for December 2025"</p>
</li>
<li><p>"Show me my hearth rate progression in 2025"</p>
</li>
</ul>
<p>Claude processes the data and provides conversational, insightful responses.</p>
<h2>The tool in action</h2>
<img src="https://cdn.hashnode.com/uploads/covers/69b6d162f4eb2f8b04a16867/601dc55f-b7d0-4bbc-85ee-5c6e25769e99.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/69b6d162f4eb2f8b04a16867/92eefe0c-8d79-4d1b-8659-cd0d1ddd7196.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/69b6d162f4eb2f8b04a16867/bdc6f979-ad4e-4a3d-a0d4-346a65c63293.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/69b6d162f4eb2f8b04a16867/7e349742-c42f-4c03-9a59-afc252e92cc6.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/69b6d162f4eb2f8b04a16867/bc3c1a81-59cb-4872-8d7e-089c61dc08a8.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/69b6d162f4eb2f8b04a16867/f0cc86bd-eeff-4a24-927f-ec0f010f2c8e.png" alt="" style="display:block;margin:0 auto" />

<h2>Try It Yourself</h2>
<p>Want to use this with your own Strava data? Here's the quick setup:</p>
<p>The code is open source and available on GitHub: <a href="https://github.com/szoboszlaianna/strava_connector">github.com/szoboszlaianna/strava_connector</a></p>
<h3>Prerequisites</h3>
<ol>
<li><p>Create a Strava API application at <a href="https://www.strava.com/settings/api">https://www.strava.com/settings/api</a></p>
</li>
<li><p>Note your Client ID and Client Secret</p>
</li>
</ol>
<h3>Installation</h3>
<pre><code class="language-shell"># Clone the repo
git clone https://github.com/szoboszlaianna/strava_connector.git
cd strava_connector

# Install dependencies
npm install

# Set up environment variables
cp .env.template .env
# Edit .env with your Client ID and Secret

# Build the project
npm run build
</code></pre>
<h3>OAuth Setup (One-time)</h3>
<pre><code class="language-shell"># Start OAuth flow
npm run oauth-setup

# Follow the instructions:
# 1. Open the provided URL in your browser
# 2. Authorize the application
# 3. Copy the authorization code from the redirect URL
# 4. Run: npm run oauth-setup YOUR_CODE

# Add the returned tokens to your .env file
</code></pre>
<h3>Configure Claude Desktop</h3>
<p>Add to your <code>claude_desktop_config.json</code>:</p>
<pre><code class="language-plaintext">{
  "mcpServers": {
    "strava": {
      "command": "node",
      "args": ["/path/to/strava_connector/build/index.js"],
      "env": {
        "STRAVA_CLIENT_ID": "your_client_id",
        "STRAVA_CLIENT_SECRET": "your_client_secret",
        "STRAVA_ACCESS_TOKEN": "your_access_token",
        "STRAVA_REFRESH_TOKEN": "your_refresh_token"
      }
    }
  }
}
</code></pre>
<p>Restart Claude Desktop, and you're ready to go!</p>
<h2>What's Next?</h2>
<p>This project is just getting started. Here are features I'm considering:</p>
<ul>
<li><p><strong>Training insights</strong>: Detect patterns like overtraining or improvement trends</p>
</li>
<li><p><strong>Goal tracking</strong>: Set monthly distance/time goals and track progress</p>
</li>
<li><p><strong>Activity recommendations</strong>: Suggest workout types based on recent training</p>
</li>
<li><p><strong>Weather correlation</strong>: Cross-reference activities with weather data</p>
</li>
<li><p><strong>Comparative analysis</strong>: Compare current performance to previous periods (This already works by fetching data for each month, but I am considering adding it's own tool)</p>
</li>
<li><p><strong>Add skills to Claude</strong>: Enhance the tool and streamline visualization with custom Claude skills. Right now it is sometimes showing me diagrams, sometimes give me written text, I would like to make sure that the output I get is consistent.</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>