name: macrofactor-mcp description: Use when reading or writing MacroFactor nutrition, food logs, weight, workout, or training program data. Triggers on meal tracking, macro counting, calorie logging, body weight, exercise logging, food search, or any MacroFactor interaction. mcp: macrofactor: command: secrets args: ['MACROFACTOR_USERNAME', 'MACROFACTOR_PASSWORD', '--', 'npx', '@sjawhar/macrofactor-mcp'] env: SOPS_AGE_KEY: '${SOPS_AGE_KEY}'
MacroFactor
Read and write nutrition, workout, and weight data via the MacroFactor MCP server. Use skill_mcp(mcp_name="macrofactor", ...) to invoke tools.
For Firestore schema details, field encodings, and common gotchas, see references/api-reference.md.
MCP Tools
Read
| Tool | Purpose |
|---|---|
get_context |
Today's snapshot: food log, macro targets + remaining, weight trend, program, next workout |
get_food_log |
Food entries for a date (default today) |
get_nutrition |
Daily macro totals for a date range |
get_weight_entries |
Scale entries for a date range |
get_steps |
Step counts for a date range |
get_profile |
User profile and preferences |
get_goals |
Current macro targets (cal, protein, carbs, fat, TDEE) |
get_workouts |
Workout history (optional date filter) |
get_workout |
Full workout detail by ID |
get_gym_profiles |
Gym profiles (id, name, equipment) |
get_custom_exercises |
User-created exercises |
get_training_program |
Active training program days and exercises |
get_next_workout |
Next workout in the program cycle |
get_custom_workouts |
List queued workout plans (app library) |
get_custom_workout |
Full custom workout plan by id |
get_training_programs |
List all training programs in the user's library |
search_foods |
Search MacroFactor food database (USDA + branded) |
search_exercises |
Search bundled exercise database by name |
Write
| Tool | Purpose | Destructive |
|---|---|---|
log_food |
Search + log a food (query + grams or amount/unit) |
No |
log_manual_food |
Log food with explicit macros (name, cal, protein, carbs, fat) | No |
log_weight |
Log scale entry (accepts lbs or kg) |
No |
log_workout |
Create workout with exercises and sets | No |
log_exercise |
Append exercises to existing workout | No |
update_food |
Update food entry quantity | No |
update_workout |
Update workout metadata (name, time, etc.) | No |
copy_food_entries |
Copy food entries between dates | No |
delete_food |
Soft-delete food entry | Yes |
hard_delete_food |
Permanently delete food entry | Yes |
delete_weight |
Delete weight entry | Yes |
delete_workout |
Delete entire workout | Yes |
remove_exercise |
Remove exercise from workout | Yes |
create_custom_exercise |
Create custom exercise with muscle/joint metadata | No |
create_custom_workout |
Queue a workout plan in the app library | No |
update_custom_workout |
Replace an existing workout plan | No |
delete_custom_workout |
Delete a queued workout plan | Yes |
create_training_program |
Create multi-day multi-cycle training program | No |
update_training_program |
Replace an existing training program | No |
delete_training_program |
Delete program (clears active if needed) | Yes |
activate_program |
Set as the active program (drives next-workout) | No |
deactivate_program |
Clear the active program | No |
Usage Examples
# What's my status today?
skill_mcp(mcp_name="macrofactor", tool_name="get_context")
# Log 150g of kale
skill_mcp(mcp_name="macrofactor", tool_name="log_food", arguments='{"query": "kale raw", "grams": 150}')
# Log food with specific macros
skill_mcp(mcp_name="macrofactor", tool_name="log_manual_food", arguments='{"name": "Protein Shake", "calories": 250, "protein": 40, "carbs": 10, "fat": 5}')
# Log weight
skill_mcp(mcp_name="macrofactor", tool_name="log_weight", arguments='{"lbs": 180}')
# Week of nutrition data
skill_mcp(mcp_name="macrofactor", tool_name="get_nutrition", arguments='{"startDate": "2026-03-17", "endDate": "2026-03-23"}')
# Log a workout
skill_mcp(mcp_name="macrofactor", tool_name="log_workout", arguments='{"name": "Push Day", "gym": "Home", "exercises": [{"name": "bench press", "sets": [{"reps": 10, "lbs": 135, "sets": 3}]}]}')
# What's next in my program?
skill_mcp(mcp_name="macrofactor", tool_name="get_next_workout")
# Create a custom exercise (reference a similar bundled exercise for metadata)
skill_mcp(mcp_name="macrofactor", tool_name="create_custom_exercise", arguments='{"name": "Cable goblet squat", "primaryFeatureMuscle": ["2545c6f170d8804bb1fbdfc4471debe5"], "exerciseMetrics": ["2555c6f170d8805cafa6d16d3fdddbaa", "2555c6f170d88072bbf6d9ad3f16ea86"], "resistanceEquipmentGroups": [{"equipmentIds": ["19e5c6f170d880d28b46e9ccddcfba1a"]}]}')
# Queue a workout for the user to do later ("workout plan" library)
skill_mcp(mcp_name="macrofactor", tool_name="create_custom_workout", arguments='{"name": "Quad Focus + Bench", "gym": "Gym", "blocks": [[{"name": "barbell back squat", "sets": [{"reps": "6-8", "rir": 2, "sets": 4}]}, {"name": "standing barbell calf raise", "sets": [{"reps": "10-12", "sets": 4}]}], [{"name": "barbell bench press", "sets": [{"reps": "6-8", "rir": 2, "sets": 4}]}]]}')
# Build a 4-week multi-day training program (cycles + deload)
skill_mcp(mcp_name="macrofactor", tool_name="create_training_program", arguments='{"name":"Push/Pull/Legs 4-cycle","numCycles":4,"isPeriodized":true,"deload":"lastCycle","gym":"Gym","days":[{"name":"Push","blocks":[[{"name":"barbell bench press","cycles":[[{"reps":"4-6","rir":2}],[{"reps":"4-6","rir":1}],[{"reps":"4-6","rir":0}],[{"reps":"8-10","rir":3}]]}]]},{"name":"Rest"}]}')
# Activate a program (set as current/next-up)
skill_mcp(mcp_name="macrofactor", tool_name="activate_program", arguments='{"id":"<program-uuid>"}')
CLI (Alternative)
The CLI is available when MCP is not. Output is JSON. Auth reads from .env automatically (do NOT source .env — password has special chars).
| Action | Command |
|---|---|
| Context snapshot | npx tsx cli/mf.ts context |
| Food log | npx tsx cli/mf.ts food-log [YYYY-MM-DD] |
| Search foods | npx tsx cli/mf.ts search-food "query" |
| Log food | npx tsx cli/mf.ts log-food '{"query":"kale","grams":150}' |
| Log manual food | npx tsx cli/mf.ts log-manual-food '{"name":"...","calories":200,"protein":30,"carbs":20,"fat":5}' |
| Log weight | npx tsx cli/mf.ts log-weight '{"lbs":180}' |
| Workouts | npx tsx cli/mf.ts workouts |
| Workout detail | npx tsx cli/mf.ts workout <uuid> |
| Log workout | npx tsx cli/mf.ts log-workout '{"name":"...","exercises":[...]}' |
| Training program | npx tsx cli/mf.ts program |
| Next workout | npx tsx cli/mf.ts next-workout |
| Nutrition range | npx tsx cli/mf.ts nutrition --from YYYY-MM-DD --to YYYY-MM-DD |
| Weight history | npx tsx cli/mf.ts weight-history --from YYYY-MM-DD --to YYYY-MM-DD |
| Goals | npx tsx cli/mf.ts goals |
| Search exercises | npx tsx cli/mf.ts exercises search "bench press" |
| Gyms | npx tsx cli/mf.ts gyms |
| Custom exercises | npx tsx cli/mf.ts custom-exercises |
| Create exercise | npx tsx cli/mf.ts create-exercise '{"name":"...","primaryFeatureMuscle":[...],...}' |
| Custom workouts (list) | npx tsx cli/mf.ts custom-workouts |
| Custom workout (read) | npx tsx cli/mf.ts custom-workout <uuid> |
| Create custom workout | npx tsx cli/mf.ts create-custom-workout '{"name":"...","gym":"Gym","blocks":[[...]]}' |
| Update custom workout | npx tsx cli/mf.ts update-custom-workout '{"id":"...","name":"...",...}' |
| Delete custom workout | npx tsx cli/mf.ts delete-custom-workout <uuid> |
| Programs (list) | npx tsx cli/mf.ts programs |
| Create program | npx tsx cli/mf.ts create-program '{"name":"...","days":[...],...}' |
| Update program | npx tsx cli/mf.ts update-program '{"id":"...","name":"...",...}' |
| Delete program | npx tsx cli/mf.ts delete-program <uuid> |
| Activate program | npx tsx cli/mf.ts activate-program <uuid> |
| Deactivate program | npx tsx cli/mf.ts deactivate-program |
Key Behaviors
- Start with
get_context— goals, today's intake vs remaining, weight trend, next workout in one call. - Dates are
YYYY-MM-DD. Most default to today when omitted. - Weight stored as kg internally.
log_weightacceptslbsorkg, converts automatically. log_foodsearches by name and logs top result. Usesearch_foodsfirst to pick a specific match. CLI also acceptsgramsfor direct gram input (e.g.grams: 80instead ofservingIndex+ fractionalquantity).log_workouttakes exercise names (not IDs) and resolves them against both bundled and custom exercises.sets: 3expands to 3 individual sets.- Food times default to now. Pass
hour/minutefor override (24h wall-clock, no timezone). - Entry IDs for
update_food/delete_foodcome fromget_food_log. - Food corrections: Use
update_foodto fix quantity — don't delete and re-log (creates ghost entries). log_manual_foodworks for custom-macro entries (e.g. restaurant meals). NEVER set the source type fieldkto"manual"— this crashes the Android app, blanking the entire day. The CLI/API already handles this correctly (uses"n").- Custom exercises: Use
create_custom_exercisewhen an exercise isn't in the bundled DB. Reference a similar bundled exercise (viasearch_exercises) to populate muscle/joint metadata. Once created, use the exercise by name inlog_workout/log_exercise. - Supersets: In
log_workout, useblocks: [[ex1, ex2]]instead ofexercises: [...]for superset grouping. - Custom workouts (workout plan library): Use
create_custom_workoutto queue a workout for the user to do later. The plan appears in the app's library tab. Sets in plans are TARGETS (rep ranges + optional RIR), not logged values. The user fills in actual reps/weight when they execute. The MCP tool automatically adds the new id toworkoutLibraryIdsso the plan surfaces in the library tab. Reps accept"6-8"range strings, single numbers, orminReps/maxRepsexplicit fields. Two app-side gotchas (verified empirically): (1) Prescribedkg/lbson plan sets is silently ignored by the app — the app's smart-progression algorithm overrides whatever you prescribe and tapers weight across multiple working sets at the same RIR target. Don't rely on per-set weights in plans; uselog_workoutfor actual weight prescriptions. (2) Plan warmups stack with the user'saddSmartWarmUps: truesetting rather than replacing it. If smart warmups are on (checkget_profile→ workout profile), omit warmups from plans entirely to avoid double warmups. - Training programs: Use
create_training_programto build a full multi-day, multi-cycle program in the app's library. Each exercise needs either explicitcycles: [[set,set],[set,set],...](one entry per cycle, each containing its set scheme) OR asets: [...]shorthand that gets repeated across allnumCyclescycles. Cross-exercise constraint: every exercise in a program must use the same number of cycles. Required field hygiene (the client handles these automatically):workoutCycleCompletions: {}andprogramExerciseIdToNote: {}must exist on new programs; rest days must usegymId: "blankSlate"and an emptyblocks: [];runtimeType: "periodized"must be set on everyperiodizedTargetseven when isPeriodized=false. Useactivate_programto make a program driveget_next_workoutand the app's home screen. - Program-linked workouts: When logging a workout for a training program, pass
workoutSourcewithprogramId,dayId, andcycleIndex. The tool will automatically (1) attach set targets fromperiodizedTargets.values[cycleIndex]and (2) updateworkoutCycleCompletionson the program document so the day shows as checked off. Useget_training_programto find dayIds andget_next_workoutfor the current cycle position. - Dashboard sync: After logging food, the CLI and MCP tools automatically trigger the app's dashboard to recompute by adding and hard-deleting a
_syncentry. This works when the app is running. If the dashboard still shows stale totals, the user can force a refresh by adding and deleting any food on that day from within the app. This is a limitation of the app's local caching — the dashboard reads from a local cache updated byFoodLogViewModel.updateNutrition, which only fires reliably when food changes go through the app's own write pipeline.
User Preferences
On first use, check for a user preferences file at ~/.config/macrofactor/preferences.md (or $XDG_CONFIG_HOME/macrofactor/preferences.md if set). If it exists, read it and follow the instructions — it contains personal context like preferred units, equipment details, workout naming conventions, etc.
If the file doesn't exist, use sensible defaults and offer to create one when you learn user-specific preferences (e.g. "You mentioned you prefer kg — want me to save that to your preferences?").
Analytical Patterns
| Analysis | Tools to combine |
|---|---|
| Weekly nutrition report | get_context → get_nutrition (7d range) → get_goals → compare daily intake vs targets |
| Training-day vs rest-day nutrition | get_training_program → get_nutrition (range) → group by training/rest days |
| TDEE estimation | get_weight_entries (30d) + get_nutrition (30d) → compare intake vs weight trend |
| Protein consistency | get_goals + get_nutrition (range) → daily protein attainment |
| Training volume analysis | get_training_program → get_workouts → get_workout per session → sets/reps by exercise |
Setup
The default frontmatter uses secrets for SOPS-encrypted credential injection. If you don't use SOPS, replace the MCP section in the frontmatter with direct env vars:
mcp:
macrofactor:
command: npx
args: ['@sjawhar/macrofactor-mcp']
env:
MACROFACTOR_USERNAME: '${MACROFACTOR_USERNAME}'
MACROFACTOR_PASSWORD: '${MACROFACTOR_PASSWORD}'
Common Agent Mistakes (avoid these)
Wrong date. Always check the system date before logging. The user often reports food/workouts that span midnight boundaries. Ask "is this for today (Apr 11) or yesterday?" when ambiguous.
Asking the user what the app shows. You have the CLI and API. Check
food-log,workouts,workout,workout-plan,next-workoutyourself before asking the user to read their screen.Guessing exercise IDs. Don't construct hex IDs from memory. Always use
exercises searchto find the correct ID. The CLI should eventually support name-based lookup inlog-workout(currently MCP-only).Wrong cycleIndex for deload. When
cycleIndex >= numCycles, use 999 (the app's sentinel). The CLI/MCP now handle this automatically, but if manually constructingworkoutSource, remember this.runtimeType must be "program". Never use "trainingProgram" — it crashes the app. The CLI/MCP enforce this.
Piping CLI output through Python. The CLI outputs JSON. Read it directly or use
workout-planwhich has human-readable output. Don't write ad-hoc Python parsers.Giving up on problems. When something doesn't work, compare raw Firestore data between app-created and CLI-created entries field by field. The answer is always in the data diff.