Using Model Context Protocol to help with my macros

April 06, 2025

Model Context Protocol allows us to integrate code with LLMs and do A LOT MORE than just generating text.

It was created by Anthropic, founded by OpenAI ex-members. Their chatbot Claude is a AI assistant.

In order to test I wanted to allow Claude to help me calculate my macros and review my diet.

First of all I need to calculate my Basal Metabolic Rate, an approximation of how many calories my body needs per day just to survive. No formula is precise and I wanted to try out different formulas and calculate de average of the results.

If you want to lose weight, your daily caloric intake should be less than your real BMR, so your body starts using body fat to keep working properly. If you want to gain weight, your caloric intake should be more than your BMR. If you are also going to the gym and want to gain muscle, having a higher caloric intake is helpful, because your body will be comfortable repairing and growing your muscles.

I started creating two Zod functions for the formulas. They don't need necessarily to be written with Zod, but it helps.

export const BmrInputSchemaFields = {
    weight: z.number().positive(), // in kg
    height: z.number().positive(), // in cm
    age: z.number().positive(),
    gender: z.enum(['male', 'female']),
    activityLevel: z.enum([
        'sedentary',
        'lightly_active',
        'moderately_active',
        'highly_active',
        'extremely_active',
    ]),
}

const BmrInputSchema = z.object(BmrInputSchemaFields)

export const calculateHarrisBenedictBMR = z
    .function()
    .args(BmrInputSchema)
    .returns(z.number())
    .implement(({ weight, height, age, gender, activityLevel }) => {
        const activityFactor = activityMultipliers[activityLevel]!
        let bmr: number
        if (gender === 'male') {
            bmr = 66 + 13.7 * weight + 5 * height - 6.8 * age
        } else {
            bmr = 655 + 9.6 * weight + 1.8 * height - 4.7 * age
        }
        return bmr * activityFactor
    })

export const calculateMifflinStJeorBMR = z
    .function()
    .args(BmrInputSchema)
    .returns(z.number())
    .implement(({ weight, height, age, gender, activityLevel }) => {
        const activityFactor = activityMultipliers[activityLevel]!
        let bmr: number
        if (gender === 'male') {
            bmr = 10 * weight + 6.25 * height - 5 * age + 5
        } else {
            bmr = 10 * weight + 6.25 * height - 5 * age - 161
        }
        return bmr * activityFactor
    })

In Model Context Protocol we first initialize a server and then define tools using "kebab-case" and the model will be smart enough to identify this tools when needed.

Observe that I had to split BmrInputSchemaFields and BmrInputSchema. For some reason MCP doesn't allow zod Objects, just a JSON with the fields.

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'

const server = new McpServer({
    name: 'Jarvis',
    version: '1.0.0',
})

server.tool(
    'calculate-harris-benedict-bmr',
    BmrInputSchemaFields,
    async (input) => {
        const res = calculateHarrisBenedictBMR(input)

        return { content: [{ type: 'text', text: String(res) }] }
    }
)

server.tool(
    'calculate-mifflin-st-jeor-bmr',
    BmrInputSchemaFields,
    async (input) => {
        const res = calculateMifflinStJeorBMR(input)

        return { content: [{ type: 'text', text: String(res) }] }
    }
)

async function main() {
    const transport = new StdioServerTransport()
    await server.connect(transport)
    console.error('MCP Server running on stdio')
}

main().catch((error) => {
    console.error('Fatal error in main():', error)
    process.exit(1)
})

To use this server we need to download Claude Desktop.

To configure Model Context Protocols you need to go to: File > Settings... > Developer > Edit Config and paste the following config in claude_desktop_config.json Remember to change the path in args to where your server is located.

{
    "mcpServers": {
        "jarvis": {
            "command": "deno",
            "args": [
                "run",
                "-A",
                "C:\\Users\\pedro\\Desktop\\apps\\jarvis\\src\\index.ts"
            ]
        }
    }
}

In this particular case, Claude should also be able to calculate my BMR without me providing the functions in TypeScript, but that makes it a lot more precise and we can be sure the results will always be the same.

This by itself is enough and Claude should be able to use this functions. But every time you prompt it will ask again for the inputs and also, it doesn't have access to which foods I have neither how many calories each contains.

I personally use Obsidian to my private notes which sit on my Desktop and have two files in it: My Data and Nutrition Facts.

import { readFileSync } from 'node:fs'

server.tool('get-data-about-me', {}, async () => {
    const text = readFileSync('C:\\Users\\jp\\Desktop\\Obsidian\\My Data.md', {
        encoding: 'utf8',
    })

    return { content: [{ type: 'text', text }] }
})

server.tool('get-data-about-my-foods', {}, async () => {
    const text = readFileSync(
        'C:\\Users\\jp\\Desktop\\Obsidian\\Nutrition Facts.md',
        { encoding: 'utf8' }
    )

    return { content: [{ type: 'text', text }] }
})

This is my Nutrion Facts table file.

| Food Item               | Quantity | kcal | Carbs | Protein | Fat  | Sodium |
|-------------------------|----------|------|-------|---------|------|--------|
| Whey with Milk          | 1        | 243  | 12.7  | 30      | 8    | 163    |
| Milk                    | 200ml    | 117  | 9.6   | 6.2     | 6    | 110    |
| Chicken                 | 100g     | 94   | 0     | 23      | 1    | 52     |
| Dulce de Leche          | 20g      | 67   | 12    | 1.5     | 1.4  | 30     |
| Rice                    | 100g     | 130  | 28    | 2.7     | 0.3  | 1      |
| Whey                    | 30g      | 126  | 3.1   | 24      | 2    | 53     |
| Light Cream Cheese      | 100g     | 154  | 1.8   | 12      | 11   | 489    |
| Oats                    | 30g      | 111  | 17    | 4.2     | 2.3  | 0      |
| Ketchup                 | 12g      | 13   | 2.9   | 0.1     | 0    | 71     |
| Beef                    | 100g     | 115  | 0     | 21      | 3.4  | 73     |
| Pork Rump               | 100g     | 166  | 0.5   | 17      | 11   | 599    |
| Seasoning               | 5g       | 11   | 0.3   | 0       | 0    | 985    |
| Peanut Butter           | 20g      | 121  | 1.6   | 5.6     | 10   | 0      |

In My Data it contains every my BmrInputSchema schema needs in a unstructured way. This is what it may looks like

Male
6,0 foot (1,8m)
30 years
Workout 7x a week
Very strong
I prefer to eat whey with milk in the morning, rice and chicken at lunch.

The model will have no trouble trying to understand this two.

Now you can ask Claude something like this and it will do all the heavy lifting for you.

using the fitness MCP I want you to read data about me and my foods and create a 4 meal diet based on the median of my BMR.

Remember than with code you can call APIs, read spreadsheets, interact with files and a lot more. The sky is the limit.


Written by João Oliveira in his brief moments of clarity.