FitTrack MCP Server
Allows querying user fitness data stored in Supabase, enabling retrieval of workouts and nutrition information.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@FitTrack MCP Servershow my recent workouts"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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: garmin-mcp
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 | Implemented |
3B | Replace placeholder workout/nutrition responses with real FitTrack data | Started with real |
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 devThis project requires Python 3.10 or newer.
Run tests:
uv run pytestStart the local MCP server over Streamable HTTP:
uv run fittrack-mcpKeep that command running while an MCP client connects.
The local MCP endpoint is:
http://127.0.0.1:8000/mcpThe connector registration handshake endpoint is:
http://127.0.0.1:8000/registerPOST /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-stdioThe MCP tools are:
get_userget_mealsget_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:
vercelAfter deployment, the MCP endpoint should be:
https://<your-vercel-project>.vercel.app/mcpUse 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@latestso Claude Desktop always runs the current bridge;use
--transport http-onlysomcp-remotestays 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
--debugenabled 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*.logmcp-remote debug logs are written under:
~/.mcp-auth/To find the newest debug log:
ls -lt ~/.mcp-auth/*debug.log | headTo read the newest debug log:
tail -n 120 ~/.mcp-auth/*debug.logIf Claude Desktop keeps using stale auth state, clear the local bridge cache and restart Claude Desktop:
rm -rf ~/.mcp-authDo 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/mcpVercel 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: 200This 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-onlytoken passed through
FITTRACK_AUTH_HEADER--debugenabled
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 HTTPURL:
https://<your-vercel-project>.vercel.app/mcpConnection Type:
DirectCustom header name:
AuthorizationCustom 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-authSend 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 |
| Added |
|
| 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_atto be later than the current time;reject missing, wrong, expired, or revoked tokens;
use the resolved
user_idto queryprofiles.id;return
profiles.full_namefrom theget_usertool.
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:
iddatetimefoodcalories
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 as7.5.hours_max: positive number upper bound, allowing decimals such as8.5.
When no sleep-hours range is provided, it defaults to hours > 0.
It returns sleep rows with:
iddatehoursnotes
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.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/taimoorahmed91/MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server