Skip to main content
Glama
taimoorahmed91

FitTrack MCP Server

FitTrack MCP Server

This project contains a Model Context Protocol (MCP) server for FitTrack. The server will let an AI assistant, such as Claude, answer questions about a user's FitTrack data after the user provides a short-lived personal access token generated inside the FitTrack app.

The detailed build plan lives in plan.txt.

Purpose

The MCP server is a separate service from the FitTrack web app. It will:

  • receive requests from an MCP-compatible AI assistant;

  • validate the user's FitTrack access token on every request;

  • resolve that token to exactly one FitTrack user;

  • read only that user's data from Supabase;

  • expose safe, focused tools for fitness questions and logging.

The FitTrack web app is not called directly by this server. Both the web app and this server read from the same Supabase database.

Related MCP server: personal-fitbit-mcp-server

Current Status

Phases 0 through 2 are complete, and Phase 3 Sub-step A is implemented.

The server runs over Streamable HTTP, validates a Bearer token from the Authorization header, and has been successfully called from Claude Desktop through mcp-remote.

The current Phase 3A code resolves real app-generated tokens through Supabase and adds a get_user tool that returns the authenticated user's full_name from the profiles table. Phase 3B has started with a real get_meals tool backed by the fittrack_meals table and a real get_sleep tool backed by the fittrack_sleep table. The old placeholder workout and nutrition tools have been removed from the published MCP server.

Planned Phases

Phase

Goal

Status

0

Local Streamable HTTP MCP server with fake responses and token checking

Complete

1

Public HTTPS deployment with fake responses

Complete

2

Online testing with Claude using the public MCP connector

Complete

3A

Supabase-backed token lookup and get_user profile lookup

Implemented

3B

Replace placeholder workout/nutrition responses with real FitTrack data

Started with real get_meals and get_sleep

4

Safety review for expiry, revocation, isolation, and rate limits

Not started

5

Everyday Claude usage

Not started

Phase 0 Scope

Phase 0 creates the smallest useful server:

  • Python project setup;

  • local Streamable HTTP MCP server entry point;

  • one shared token-checking checkpoint;

  • one known hardcoded token fingerprint;

  • fake tools such as recent workouts or today's nutrition;

  • clear rejection when the token is missing or invalid.

Phase 0 should not include Supabase, hosting, real user data, Google login, or production secrets.

Running Locally

Install dependencies:

uv sync --extra dev

This project requires Python 3.10 or newer.

Run tests:

uv run pytest

Start the local MCP server over Streamable HTTP:

uv run fittrack-mcp

Keep that command running while an MCP client connects.

The local MCP endpoint is:

http://127.0.0.1:8000/mcp

The connector registration handshake endpoint is:

http://127.0.0.1:8000/register

POST /register is intentionally allowed without a Bearer token. Tool calls and other MCP requests still require Authorization: Bearer <token>.

On Vercel, /register is routed to a standalone function at api/register.py so it cannot be intercepted by the MCP Bearer token middleware.

For clients that specifically need stdio instead of HTTP, use:

uv run fittrack-mcp-stdio

The MCP tools are:

  • get_user

  • get_meals

  • get_sleep

The token is not a tool argument. MCP tool-call requests must include this HTTP header:

Authorization: Bearer <token>

Wrong or missing authorization headers on tool calls return a JSON-RPC error:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32001,
    "message": "authentication failed"
  }
}

The server deliberately avoids returning HTTP 401 for MCP tool-call auth failures because some MCP clients interpret 401 as a signal to start an OAuth flow. FitTrack MCP uses the custom Authorization: Bearer <token> header instead.

Phase 1 Deployment

Phase 1 deploys the same fake-data MCP server to a public HTTPS URL.

Deploy with Vercel:

vercel

After deployment, the MCP endpoint should be:

https://<your-vercel-project>.vercel.app/mcp

Use a real app-generated FitTrack token as an Authorization: Bearer ... header once the Supabase environment variables are configured.

The deployment entrypoint is app.py, which exposes the MCP server as an ASGI app for Vercel.

For Vercel, the deployed ASGI app explicitly starts FastMCP's Streamable HTTP session manager around each serverless request. This avoids POST /mcp crashes when the platform does not run Starlette lifespan startup before invoking the function.

Deployed mode also uses JSON responses for MCP POST requests. This is friendlier for Vercel and browser-based tools such as MCP Inspector than holding each POST open as an event stream.

Claude Code Setup

This server uses custom Bearer-token authentication, not OAuth. Add it to Claude Code with the token header configured up front:

claude mcp add --transport http fittrack https://<your-vercel-project>.vercel.app/mcp \
  --header "Authorization: Bearer <real-app-generated-token>"

If the FitTrack token expires, remove and re-add the server with a fresh token, or configure Claude Code with a headersHelper that prints:

{"Authorization": "Bearer <real-app-generated-token>"}

Do not rely on /mcp OAuth authentication for this server. /register is allowed for connector compatibility, but it is not a full OAuth dynamic client registration flow.

Claude Desktop Setup

Claude Desktop connects to remote MCP servers through a local stdio bridge. For this project, the bridge is mcp-remote.

The working Claude Desktop configuration is:

{
  "mcpServers": {
    "fittrack": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://mcp-khaki-two.vercel.app/mcp",
        "--transport",
        "http-only",
        "--header",
        "Authorization:${FITTRACK_AUTH_HEADER}",
        "--debug"
      ],
      "env": {
        "FITTRACK_AUTH_HEADER": "Bearer <real-app-generated-token>"
      }
    }
  }
}

Replace <real-app-generated-token> with the same FitTrack token that works in MCP Inspector or /debug-auth.

Important details:

  • use mcp-remote@latest so Claude Desktop always runs the current bridge;

  • use --transport http-only so mcp-remote stays on Streamable HTTP;

  • pass the token as an environment variable so the required space in Bearer <token> is preserved;

  • keep the header argument as Authorization:${FITTRACK_AUTH_HEADER};

  • keep --debug enabled while diagnosing Claude Desktop connection issues.

After editing Claude Desktop's config file, fully quit and reopen Claude Desktop. A normal window close is not always enough for Claude to reload MCP configuration.

On macOS, Claude Desktop logs can be watched with:

tail -n 80 -F ~/Library/Logs/Claude/mcp*.log

mcp-remote debug logs are written under:

~/.mcp-auth/

To find the newest debug log:

ls -lt ~/.mcp-auth/*debug.log | head

To read the newest debug log:

tail -n 120 ~/.mcp-auth/*debug.log

If Claude Desktop keeps using stale auth state, clear the local bridge cache and restart Claude Desktop:

rm -rf ~/.mcp-auth

Do this only when you are ready to reconnect the MCP server and re-create the local mcp-remote state.

Vercel Hosting Notes

The current deployment is hosted on Vercel at:

https://mcp-khaki-two.vercel.app/mcp

Vercel can run this project well enough for the current Claude Desktop setup, but there is an important platform caveat.

Streamable HTTP includes a long-lived GET /mcp request for server-sent events. Claude Desktop, through mcp-remote, may keep that request open in the background. Vercel's Python serverless runtime eventually kills long-running requests. In Vercel logs this appears as:

Vercel Runtime Timeout Error: Task timed out after 300 seconds
requestMethod: GET
requestPath: /mcp
responseStatusCode: 200

This timeout means Vercel killed the open stream. It does not automatically mean the Bearer token is wrong or that Supabase failed.

The current mitigation is the Claude Desktop config above:

  • mcp-remote@latest

  • --transport http-only

  • token passed through FITTRACK_AUTH_HEADER

  • --debug enabled

If this becomes unreliable in daily use, move the same app to a host that is designed for long-lived HTTP connections, such as Railway, Render, Fly.io, Cloud Run, or a small VPS.

MCP Inspector

Use these MCP Inspector settings:

  • Transport Type: Streamable HTTP

  • URL: https://<your-vercel-project>.vercel.app/mcp

  • Connection Type: Direct

  • Custom header name: Authorization

  • Custom header value: Bearer <real-app-generated-token>

The Vercel ASGI app includes CORS support so browser-based direct connections can send the Authorization header.

Inspector is useful for proving that the server, Bearer token, Supabase lookup, and tool schemas work. Claude Desktop is the more important end-to-end test because it adds the mcp-remote bridge and a persistent background connection.

Auth Debugging

If get_user returns MCP error -32001: authentication failed, test the same Bearer token against:

https://<your-vercel-project>.vercel.app/debug-auth

Send the same header:

Authorization: Bearer <real-app-generated-token>

The response does not expose the token or service role key. It reports which stage failed: missing header, missing Supabase environment variables, Supabase HTTP/network error, token hash not found, expired token row, or authenticated user ID.

A successful debug response looks like:

{
  "ok": true,
  "stage": "authenticated",
  "user_id": "cf439197-c0cf-487d-936f-fe289a68bb41"
}

If /debug-auth succeeds but Claude Desktop fails, look at the Claude Desktop and mcp-remote logs before changing server code. That usually means the issue is in the client bridge, cached auth state, or hosting connection behavior.

Environment Variables

These Phase 3A variables have been added in Vercel:

Variable

Vercel status

Value

SUPABASE_URL

Added

https://nywsjgxlnilmcztnvidc.supabase.co

SUPABASE_SERVICE_ROLE_KEY

Added

Stored only in Vercel, not committed

The service role key must only be stored in the deployment environment. Do not commit it to Git.

Claude Desktop Test

Claude Desktop has successfully connected to the public MCP server and used the real Supabase-backed tools from plain-language requests:

who am I?
what did I eat today?

The response returned the authenticated user's real profile name and real meal rows from Supabase. This confirms the connector can load the server, discover the tools, choose a tool, send the Bearer token, resolve the token to a user, and receive a scoped Supabase-backed response.

Phase 3A

Phase 3A replaces the local hardcoded token fingerprint with a Supabase lookup:

  • read Authorization: Bearer <token> from each request;

  • hash the token with SHA-256;

  • look up the fingerprint in fittrack_api_tokens.token_hash;

  • require fittrack_api_tokens.expires_at to be later than the current time;

  • reject missing, wrong, expired, or revoked tokens;

  • use the resolved user_id to query profiles.id;

  • return profiles.full_name from the get_user tool.

The get_user tool has no inputs and is described to clients as:

Returns the full name of the authenticated FitTrack user. No inputs required.

Phase 3B Next Step

Phase 3B has started with get_meals and get_sleep.

The get_meals tool reads from fittrack_meals, scoped to the user_id resolved from the Bearer token. It accepts optional inputs:

  • date: YYYY-MM-DD; defaults to today's date when omitted.

  • calories_min: positive integer lower bound.

  • calories_max: positive integer upper bound.

When no calorie range is provided, it defaults to calories > 0.

It returns meal rows with:

  • id

  • date

  • time

  • food

  • calories

Claude Desktop has successfully called get_meals through the deployed Vercel MCP server and returned real meals for June 27:

Time

Food

Calories

09:20

200g banana, 150ml milk, 2 tsp sugar

240

12:06

2 scoops whey

292

12:45

3 boiled eggs, 4 toast, 2 tsp mayo

570

19:40

300g chicken breast, 120g roti, 50g yogurt

695

Total returned calories: 1,797.

Get Sleep Tool

The get_sleep tool reads from fittrack_sleep, scoped to the user_id resolved from the Bearer token. It accepts optional inputs:

  • date: YYYY-MM-DD; defaults to today's date when omitted.

  • hours_min: positive number lower bound, allowing decimals such as 7.5.

  • hours_max: positive number upper bound, allowing decimals such as 8.5.

When no sleep-hours range is provided, it defaults to hours > 0.

It returns sleep rows with:

  • id

  • date

  • hours

  • notes

The tool is described to clients as:

Returns sleep entries for the authenticated FitTrack user. Optional inputs: date as YYYY-MM-DD, hours_min, and hours_max. If date is omitted, today's date is used. If no sleep-hours range is provided, only entries with hours greater than zero are returned.

Remaining Phase 3B work: add more real Supabase-backed tools as needed.

Security Principles

  • The token is the identity.

  • The assistant never gets to claim which user it is acting for.

  • Every request is authenticated independently.

  • Token checking happens in one shared place.

  • Real tokens should never be stored directly, only their one-way fingerprints.

  • Once Supabase is connected, every data query must be scoped to the user resolved from the token.

Notes

The intended implementation language is Python, using the standard MCP toolkit. Hosting is expected to start with Vercel, with Railway or Render as fallback options if the server shape fits those platforms better.

Available Tools

3 tools
get_mealsA

Returns meals for the authenticated FitTrack user. Optional inputs: date as YYYY-MM-DD, calories_min, and calories_max. If date is omitted, today's date is used. If no calorie range is provided, only meals with calories greater than zero are returned.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateNo
calories_maxNo
calories_minNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description discloses key behaviors: authentication requirement, date default, and calorie filtering default. It does not mention rate limits or destructive actions, but as a read-only tool, this is sufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three concise sentences, front-loaded with the main purpose, no redundant words. Every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has an output schema, the description adequately covers input behavior and authentication. It is complete enough for a simple data retrieval tool, though it could optionally mention the lack of pagination or sorting.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds meaning beyond the schema: date format YYYY-MM-DD, default for date, and the effect of omitting calorie ranges. However, it does not clarify behavior when only one of calories_min or calories_max is provided, leaving a slight gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Returns meals for the authenticated FitTrack user,' specifying the verb (returns) and resource (meals) with user scope, which distinguishes it from sibling tools like get_sleep and get_user.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains when to use parameters and defaults (date omitted uses today, no calorie range returns meals >0), but does not explicitly contrast with siblings or state when not to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_sleepA

Returns sleep entries for the authenticated FitTrack user. Optional inputs: date as YYYY-MM-DD, hours_min, and hours_max. If date is omitted, today's date is used. If no sleep-hours range is provided, only entries with hours greater than zero are returned.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateNo
hours_maxNo
hours_minNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description carries burden. It discloses default behaviors but does not discuss authentication failures, rate limits, or read-only nature (though implied). Adequate but could add more.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no wasted words, front-loaded with purpose. Efficient and clear.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, description doesn't need return format. Covers all input behaviors, default actions. Sibling tools not referenced but not needed. Nearly complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so description compensates well. Explains date format, default value, and the effect of omitting hours_min/max (returns entries with hours > 0). Adds significant meaning beyond schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it returns sleep entries for the authenticated user, specifying the resource and action. It does not explicitly distinguish from siblings get_meals and get_user, but the name and context make it distinct.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear guidance on optional parameters, date format, default date behavior, and hourly range filtering. Does not mention when to avoid using or alternative tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_userA

Returns the full name of the authenticated FitTrack user. No inputs required.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It only states what is returned without mentioning authentication requirements, error conditions, or side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence that is concise and front-loaded, providing all necessary information without extraneous content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With output schema existing, the description sufficiently covers the tool's purpose, though it doesn't mention non-name fields that might be returned. Fairly complete for a simple tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

No parameters exist, and schema coverage is 100%, so baseline score of 4 applies. The description adds no parameter info, but none is needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states the tool returns the full name of the authenticated FitTrack user with no inputs, and distinguishes from siblings like get_meals and get_sleep.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implicitly indicates it's for retrieving user identity with no inputs, but lacks explicit guidance on when not to use or alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedget_meals
    • First observedget_sleep
    • First observedget_user

TDQS

A3.9/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct resource: meals, sleep, and user info. There is no overlap in purpose, ensuring clear selection by an agent.

Naming Consistency5/5

All tool names follow a consistent 'get_' prefix with a noun (get_meals, get_sleep, get_user). No mixing of conventions.

Tool Count4/5

With only 3 tools, the server feels slightly thin but still appropriate for a focused read-only fitness data retrieval service. The scope is limited but coherent.

Completeness2/5

The server only provides read operations (GET) with no ability to create, update, or delete data. For a fitness tracker, this is a significant gap, limiting agent capabilities to querying existing data.

Maintenance

ActivityInactive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers