← Back to the photo testerDownload raw .md
Food Logger MCP

Food Logger MCP — Integration Guide

A remote Model Context Protocol server for food logging and nutrition tracking. Connect any MCP client (Claude Desktop/Code, Cursor, your own app via the MCP SDK) and search foods, log meals, analyze meal photos, and pull daily summaries and reports — all in natural language or via structured tool calls.

Use this hosted MCP server when you need an AI food diary, nutrition API, calorie estimator, macro tracker, food search tool, or meal photo analysis endpoint for Claude, Cursor, GitHub Copilot, or another MCP-compatible client.


1. Authentication & multi-user

The bearer token is the identity. It's hashed to an opaque user_id, and every food, entry, and target is scoped to it — distinct tokens get fully isolated diaries (your data is never visible to another token). Requests with a missing or unauthorized token get 401.

To get a token, sign in by email at https://foodlog.sig.ai/account. New accounts receive a magic link that creates one trial token with 5 free photo-analysis credits. Existing users receive a freshly rotated token when they sign in. The token is saved locally in that browser and can be revealed/copied from the account page. Paid monthly plans add more credits via Stripe: Starter ($1/mo, 50 credits), Pro ($10/mo, 1,000 credits), or Unlimited ($200/mo, fair-use). Email mcp@sig.ai for help. Paid users can manage billing from the account page through Stripe Customer Portal.

Treat the token like a password — anyone holding it can read and write that diary and spend the account's photo-analysis credits.

Authorization: Bearer YOUR_TOKEN

2. Connecting

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "food-logger": {
      "type": "http",
      "url": "https://foodlog.sig.ai/api/mcp",
      "headers": { "Authorization": "Bearer YOUR_TOKEN" }
    }
  }
}

Claude Code (CLI)

claude mcp add --transport http food-logger https://foodlog.sig.ai/api/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"

TypeScript / Node (programmatic)

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://foodlog.sig.ai/api/mcp"),
  { requestInit: { headers: { Authorization: "Bearer YOUR_TOKEN" } } },
);
const client = new Client({ name: "my-app", version: "1.0.0" });
await client.connect(transport);

const result = await client.callTool({
  name: "foodlog_log_food",
  arguments: { name: "Latte", meal: "breakfast", nutrients: { calories: 120, protein_g: 6 } },
});
console.log(result.content[0].text);

Python (programmatic)

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async with streamablehttp_client(
    "https://foodlog.sig.ai/api/mcp",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
) as (read, write, _):
    async with ClientSession(read, write) as session:
        await session.initialize()
        res = await session.call_tool(
            "foodlog_get_daily_summary", {"response_format": "json"}
        )
        print(res.content[0].text)

Raw JSON-RPC (no SDK)

The server is stateless: a single tools/call works without a prior initialize handshake — you don't need to manage a session. Two requirements:

  1. The Accept header must advertise both application/json and text/event-stream (sending only application/json returns 406).
  2. The response is SSE-framed (event: message\ndata: {…}), so strip the data: prefix to get the JSON-RPC envelope.
curl -s https://foodlog.sig.ai/api/mcp \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"foodlog_get_daily_summary","arguments":{"response_format":"json"}}}'
# → event: message
#   data: {"result":{"content":[{"type":"text","text":"{ ...the tool's JSON... }"}]},"jsonrpc":"2.0","id":1}

The tool's structured payload is the JSON string in result.content[0].text — parse it a second time to get the object. An MCP SDK handles the SSE framing and this unwrapping for you, and is recommended for anything beyond one-shot calls.


3. Data model

Nutrients (all optional, per 100 g on foods, as-consumed totals on entries):

calories · protein_g · carbs_g · fat_g · fiber_g · sugar_g · sodium_mg · saturated_fat_g

Macros is the explicit subset { carbs_g, fat_g, protein_g }, surfaced as a sibling field in JSON responses (macros, macros_per_100g, average_macros_per_logged_day).

Food references (food_ref) — every food has a stable id:

FormMeaning
local:<id>A custom food you created
off:<barcode>Open Food Facts product
fdc:<id>USDA FoodData Central food
<8–14 digits>A bare barcode, treated as off:<barcode>

Unitsg and oz (28.3495 g) are exact; ml uses density 1.0 (accurate for water-like liquids); serving requires the food to define serving_size_g.

Mealsbreakfast · lunch · dinner · snack.

Response format — most read tools and diary/custom-food write tools accept response_format: "markdown" (default, human-readable) or "json" (structured, includes macro groups). Diary write JSON includes day for the entry's resulting date plus affected_days for every local date whose totals changed. Pass notes: null to clear an entry note or to repeat a meal without copying the original note. export_entries uses format: "csv" or "json" because CSV is the primary spreadsheet/export mode.


4. Tool reference

All tool names are prefixed foodlog_. Required args in bold.

Account & usage

ToolKey argumentsReturns
get_account_statusresponse_formatCurrent token type, plan, subscription status, remaining photo-analysis credits, and account/support links
get_credit_historylimit (1–50), response_formatRecent credit grants, spends, refunds, and current credit balance for self-serve accounts

Discovery

ToolKey argumentsReturns
search_foodsquery (name or barcode), source (all|local|openfoodfacts|usda), limit (1–50), page, response_formatMatching foods with food_refs, stable source groups, and next-page hints
get_foodfood_ref, response_formatFull details for one food (external foods are cached locally)
create_custom_foodname, nutrients_per_100g, brand, serving_size_g, serving_description, response_formatThe new food incl. its local:<id> ref
update_custom_foodfood_ref (local:<id> only), any of name, brand, nutrients_per_100g, serving_size_g, serving_description, response_formatUpdates a reusable custom food for future logs; existing entries keep their snapshotted nutrients
delete_custom_foodfood_ref (local:<id> only), response_formatRemoves a reusable custom food; existing diary entries are not deleted

Diary

ToolKey argumentsReturns
log_foodmeal, plus either food_ref + quantity/unit, or name + nutrients (one-off). logged_at, log_date, timezone, notes (string or null), response_formatThe saved entry + the day's running totals
repeat_entryentry_id, meal, logged_at, log_date, timezone, notes (string or null), response_formatCopies an existing diary entry to today or another date, preserving the original amount and nutrition snapshot
get_entryentry_id, response_formatOne diary entry by id, including snapshotted nutrients and macros
list_entriesstart_date, end_date, meal, limit (1–100), offset, response_formatEntries (newest first) + pagination metadata
export_entriesstart_date, end_date, meal, format (csv|json), limit (1–1000), offsetCSV or JSON diary export for backup, spreadsheet analysis, or migration
update_entryentry_id, plus any of quantity, unit, meal, logged_at, log_date, timezone, notes (string or null), nutrients, response_formatThe updated entry + every affected day's running totals
delete_entryentry_id, response_formatConfirmation and, in JSON mode, the deleted entry snapshot plus updated day totals

Insights & goals

ToolKey argumentsReturns
get_daily_summarydate (default today), timezone, response_formatTotals, per-meal breakdown, macros, progress vs targets
get_reportstart_date, end_date (≤ 92 inclusive days), response_formatPer-day totals + per-logged-day averages
get_frequent_foodsstart_date, end_date, meal, limit (1–50), offset, response_formatFoods ranked by log frequency, with latest entry id for repeat logging and average macros per entry
get_top_contributorsdate, or start_date + end_date (≤ 92 inclusive days), meal, nutrient, limit (1–25), timezone, response_formatFoods ranked by contribution to calories, macros, sugar, sodium, fiber, saturated fat, or another tracked nutrient
get_remaining_targetsdate (default today), timezone, response_formatCalories, macros, and nutrients remaining or over target for one day
get_logging_consistencystart_date, end_date, timezone, response_formatDays logged, missed dates, current streak, longest streak, and entry frequency; defaults to the last 30 days
get_target_progressstart_date, end_date, timezone, response_formatTotals and logged-day/calendar-day averages compared with targets in effect on the end date
get_meal_breakdowndate, or start_date + end_date (≤ 92 inclusive days), timezone, response_formatCalories, macros, and percent-of-total by meal over one day or a date range
set_targetstargets, effective_date (default today), timezone, response_formatThe targets now in effect (versioned by date), including macro groups in JSON mode
delete_targetseffective_date for one exact version, or delete_all: true for all versions, response_formatDeletes mistaken or obsolete daily target versions
get_targetsdate (default today), timezone, response_formatThe targets in effect on that date

Photo analysis

ToolKey argumentsReturns
analyze_meal_photoEither image_url or image_base64 (+ media_type), hints, log (bool), meal (required if log), logged_at, log_date, timezone, response_formatIdentified items with estimated grams + nutrients + macros, meal totals, and (if log=true) created diary entries

Notes on the photo tool:


5. Example JSON responses

get_account_status (response_format: "json"):

{
  "account_type": "self_serve",
  "email": "you@example.com",
  "plan": "starter",
  "subscription_status": "active",
  "credits_remaining": 42,
  "unlimited": false,
  "can_analyze_photos": true,
  "photo_analysis_credit_cost": 1,
  "metered_photo_analysis": true,
  "token_prefix": "flm_abcd...wxyz",
  "account_url": "https://foodlog.sig.ai/account",
  "support_email": "mcp@sig.ai"
}

get_credit_history (response_format: "json"):

{
  "account_type": "self_serve",
  "email": "you@example.com",
  "plan": "starter",
  "subscription_status": "active",
  "credits_remaining": 42,
  "unlimited": false,
  "events": [
    {
      "delta": -1,
      "reason": "photo_analysis",
      "label": "Photo analysis",
      "plan": "starter",
      "created_at": "2026-06-12T18:30:00.000Z"
    },
    {
      "delta": 50,
      "reason": "stripe_subscription_renewal",
      "label": "Subscription credits",
      "plan": "starter",
      "created_at": "2026-06-01T12:00:00.000Z"
    }
  ],
  "account_url": "https://foodlog.sig.ai/account",
  "support_email": "mcp@sig.ai"
}

search_foods (response_format: "json")sources preserves the human-readable legacy buckets; use source_groups for stable machine parsing:

{
  "query": "greek yogurt",
  "page": 1,
  "limit": 2,
  "source": "all",
  "sources": {
    "Your foods (local)": [],
    "Open Food Facts": [
      {
        "food_ref": "off:1234567890123",
        "source": "off",
        "name": "Greek Yogurt",
        "brand": "Example",
        "serving_size_g": 170,
        "serving_description": "1 container",
        "nutrients_per_100g": { "calories": 59, "protein_g": 10, "carbs_g": 3.6, "fat_g": 0.4 },
        "macros_per_100g": { "carbs_g": 3.6, "fat_g": 0.4, "protein_g": 10 }
      }
    ]
  },
  "source_groups": [
    {
      "source": "local",
      "label": "Your foods (local)",
      "page": 1,
      "limit": 2,
      "count": 0,
      "has_more_hint": false,
      "next_page_hint": null,
      "foods": []
    },
    {
      "source": "openfoodfacts",
      "label": "Open Food Facts",
      "page": 1,
      "limit": 2,
      "count": 1,
      "has_more_hint": false,
      "next_page_hint": null,
      "foods": [
        {
          "food_ref": "off:1234567890123",
          "source": "off",
          "name": "Greek Yogurt",
          "brand": "Example",
          "serving_size_g": 170,
          "serving_description": "1 container",
          "nutrients_per_100g": { "calories": 59, "protein_g": 10, "carbs_g": 3.6, "fat_g": 0.4 },
          "macros_per_100g": { "carbs_g": 3.6, "fat_g": 0.4, "protein_g": 10 }
        }
      ]
    }
  ],
  "next_page_hint": null,
  "notes": ["USDA FoodData Central not searched (FDC_API_KEY not configured)."]
}

update_custom_food (response_format: "json"):

{
  "updated": true,
  "food": {
    "food_ref": "local:9",
    "source": "local",
    "name": "Greek yogurt bowl",
    "brand": null,
    "serving_size_g": 350,
    "serving_description": "1 bowl",
    "nutrients_per_100g": { "calories": 117, "protein_g": 9.1, "carbs_g": 12.9, "fat_g": 3.4 },
    "macros_per_100g": { "carbs_g": 12.9, "fat_g": 3.4, "protein_g": 9.1 }
  },
  "history_note": "Existing diary entries keep their snapshotted nutrients; future logs use this updated custom food."
}

delete_custom_food (response_format: "json"):

{
  "deleted": true,
  "food": {
    "food_ref": "local:9",
    "source": "local",
    "name": "Greek yogurt bowl",
    "brand": null,
    "serving_size_g": 350,
    "serving_description": "1 bowl",
    "nutrients_per_100g": { "calories": 117, "protein_g": 9.1, "carbs_g": 12.9, "fat_g": 3.4 },
    "macros_per_100g": { "carbs_g": 12.9, "fat_g": 3.4, "protein_g": 9.1 }
  },
  "history_note": "Existing diary entries were not deleted; they keep their snapshotted nutrients."
}

create_custom_food (response_format: "json"):

{
  "created": true,
  "food": {
    "food_ref": "local:18",
    "source": "local",
    "name": "Protein oats",
    "brand": "House",
    "serving_size_g": 300,
    "serving_description": "1 bowl",
    "nutrients_per_100g": { "calories": 143, "protein_g": 10, "carbs_g": 19, "fat_g": 3.3 },
    "macros_per_100g": { "carbs_g": 19, "fat_g": 3.3, "protein_g": 10 }
  }
}

get_daily_summary (response_format: "json") — note totals stays a flat nutrient map; macros is a sibling, and each meal carries its own macros:

{
  "date": "2026-06-11",
  "entry_count": 2,
  "totals": { "calories": 750, "protein_g": 60, "carbs_g": 40, "fat_g": 30 },
  "macros": { "carbs_g": 40, "fat_g": 30, "protein_g": 60 },
  "meals": {
    "breakfast": {
      "entry_count": 1, "calories": 300, "protein_g": 30,
      "macros": { "protein_g": 30 }
    }
  },
  "targets": {
    "effective_date": "2026-06-01",
    "targets": { "calories": 2000, "protein_g": 120 },
    "macros": { "protein_g": 120 }
  }
}

get_entry (response_format: "json"):

{
  "entry_id": 45,
  "name": "Greek yogurt bowl",
  "brand": null,
  "meal": "breakfast",
  "quantity": 1,
  "unit": "serving",
  "grams": 350,
  "log_date": "2026-06-12",
  "logged_at": "2026-06-12T15:30:00.000Z",
  "food_ref": "local:9",
  "notes": "with blueberries",
  "nutrients": { "calories": 410, "protein_g": 32, "carbs_g": 45, "fat_g": 12 },
  "macros": { "carbs_g": 45, "fat_g": 12, "protein_g": 32 }
}

log_food (response_format: "json"):

{
  "created": true,
  "entry": {
    "entry_id": 46,
    "name": "Turkey sandwich",
    "brand": null,
    "meal": "lunch",
    "quantity": 1,
    "unit": "serving",
    "grams": null,
    "log_date": "2026-06-12",
    "logged_at": "2026-06-12T19:10:00.000Z",
    "food_ref": null,
    "notes": null,
    "nutrients": { "calories": 520, "protein_g": 34, "carbs_g": 48, "fat_g": 21 },
    "macros": { "carbs_g": 48, "fat_g": 21, "protein_g": 34 }
  },
  "day": {
    "date": "2026-06-12",
    "entry_count": 4,
    "totals": { "calories": 2070, "protein_g": 126, "carbs_g": 218, "fat_g": 76 },
    "macros": { "carbs_g": 218, "fat_g": 76, "protein_g": 126 }
  },
  "affected_days": [
    {
      "date": "2026-06-12",
      "entry_count": 4,
      "totals": { "calories": 2070, "protein_g": 126, "carbs_g": 218, "fat_g": 76 },
      "macros": { "carbs_g": 218, "fat_g": 76, "protein_g": 126 }
    }
  ]
}

repeat_entry (response_format: "json"):

{
  "source_entry_id": 12,
  "entry": {
    "entry_id": 45,
    "food_ref": "local:9",
    "name": "Greek yogurt bowl",
    "brand": null,
    "meal": "breakfast",
    "quantity": 1,
    "unit": "serving",
    "log_date": "2026-06-12",
    "nutrients": { "calories": 410, "protein_g": 32, "carbs_g": 45, "fat_g": 12 },
    "macros": { "carbs_g": 45, "fat_g": 12, "protein_g": 32 }
  },
  "day": {
    "date": "2026-06-12",
    "entry_count": 3,
    "totals": { "calories": 1550, "protein_g": 92, "carbs_g": 170, "fat_g": 55 },
    "macros": { "carbs_g": 170, "fat_g": 55, "protein_g": 92 }
  },
  "affected_days": [
    {
      "date": "2026-06-12",
      "entry_count": 3,
      "totals": { "calories": 1550, "protein_g": 92, "carbs_g": 170, "fat_g": 55 },
      "macros": { "carbs_g": 170, "fat_g": 55, "protein_g": 92 }
    }
  ]
}

update_entry (response_format: "json"):

{
  "updated": true,
  "entry": {
    "entry_id": 46,
    "name": "Turkey sandwich",
    "brand": null,
    "meal": "lunch",
    "quantity": 0.5,
    "unit": "serving",
    "grams": null,
    "log_date": "2026-06-13",
    "logged_at": "2026-06-12T19:10:00.000Z",
    "food_ref": null,
    "notes": null,
    "nutrients": { "calories": 260, "protein_g": 17, "carbs_g": 24, "fat_g": 10.5 },
    "macros": { "carbs_g": 24, "fat_g": 10.5, "protein_g": 17 }
  },
  "day": {
    "date": "2026-06-13",
    "entry_count": 1,
    "totals": { "calories": 260, "protein_g": 17, "carbs_g": 24, "fat_g": 10.5 },
    "macros": { "carbs_g": 24, "fat_g": 10.5, "protein_g": 17 }
  },
  "affected_days": [
    {
      "date": "2026-06-12",
      "entry_count": 3,
      "totals": { "calories": 1550, "protein_g": 92, "carbs_g": 170, "fat_g": 55 },
      "macros": { "carbs_g": 170, "fat_g": 55, "protein_g": 92 }
    },
    {
      "date": "2026-06-13",
      "entry_count": 1,
      "totals": { "calories": 260, "protein_g": 17, "carbs_g": 24, "fat_g": 10.5 },
      "macros": { "carbs_g": 24, "fat_g": 10.5, "protein_g": 17 }
    }
  ]
}

delete_entry (response_format: "json"):

{
  "deleted": true,
  "entry": {
    "entry_id": 46,
    "name": "Turkey sandwich",
    "brand": null,
    "meal": "lunch",
    "quantity": 0.5,
    "unit": "serving",
    "grams": null,
    "log_date": "2026-06-12",
    "logged_at": "2026-06-12T19:10:00.000Z",
    "food_ref": null,
    "notes": "half sandwich",
    "nutrients": { "calories": 260, "protein_g": 17, "carbs_g": 24, "fat_g": 10.5 },
    "macros": { "carbs_g": 24, "fat_g": 10.5, "protein_g": 17 }
  },
  "day": {
    "date": "2026-06-12",
    "entry_count": 3,
    "totals": { "calories": 1550, "protein_g": 92, "carbs_g": 170, "fat_g": 55 },
    "macros": { "carbs_g": 170, "fat_g": 55, "protein_g": 92 }
  },
  "affected_days": [
    {
      "date": "2026-06-12",
      "entry_count": 3,
      "totals": { "calories": 1550, "protein_g": 92, "carbs_g": 170, "fat_g": 55 },
      "macros": { "carbs_g": 170, "fat_g": 55, "protein_g": 92 }
    }
  ]
}

get_frequent_foods (response_format: "json"):

{
  "filters": { "start_date": "2026-06-01", "end_date": "2026-06-12", "meal": "breakfast" },
  "total": 1,
  "count": 1,
  "offset": 0,
  "has_more": false,
  "next_offset": null,
  "foods": [
    {
      "food_ref": "local:9",
      "name": "Greek yogurt bowl",
      "brand": null,
      "times_logged": 5,
      "first_log_date": "2026-06-01",
      "last_log_date": "2026-06-12",
      "most_recent_entry_id": 45,
      "average_per_entry": { "calories": 410, "protein_g": 32, "carbs_g": 45, "fat_g": 12 },
      "average_macros_per_entry": { "carbs_g": 45, "fat_g": 12, "protein_g": 32 }
    }
  ]
}

get_remaining_targets (response_format: "json"):

{
  "date": "2026-06-12",
  "entry_count": 3,
  "target_effective_date": "2026-06-01",
  "consumed": { "calories": 1550, "protein_g": 132, "carbs_g": 170, "fat_g": 55 },
  "consumed_macros": { "carbs_g": 170, "fat_g": 55, "protein_g": 132 },
  "targets": { "calories": 2000, "protein_g": 120, "carbs_g": 170, "fat_g": 70 },
  "target_macros": { "carbs_g": 170, "fat_g": 70, "protein_g": 120 },
  "remaining": { "calories": 450, "fat_g": 15 },
  "remaining_macros": { "fat_g": 15 },
  "over_by": { "protein_g": 12 },
  "over_by_macros": { "protein_g": 12 },
  "progress": {
    "calories": { "target": 2000, "consumed": 1550, "remaining": 450, "over_by": 0, "percent": 77.5, "status": "under" },
    "protein_g": { "target": 120, "consumed": 132, "remaining": 0, "over_by": 12, "percent": 110, "status": "over" }
  }
}

get_top_contributors (response_format: "json"):

{
  "start_date": "2026-06-01",
  "end_date": "2026-06-07",
  "meal": null,
  "nutrient": "calories",
  "total_entries": 8,
  "total_value": 4300,
  "scanned_entries": 8,
  "entry_cap": 1000,
  "truncated": false,
  "contributors": [
    {
      "food_ref": "local:9",
      "name": "Rice bowl",
      "brand": null,
      "entry_count": 3,
      "first_log_date": "2026-06-01",
      "last_log_date": "2026-06-06",
      "latest_entry_id": 45,
      "total_grams": 1250,
      "average_grams": 416.67,
      "value": 1850,
      "percent_of_total": 43.02,
      "totals": { "calories": 1850, "protein_g": 92, "carbs_g": 230, "fat_g": 54 },
      "macros": { "carbs_g": 230, "fat_g": 54, "protein_g": 92 }
    }
  ]
}

get_meal_breakdown (response_format: "json"):

{
  "start_date": "2026-06-01",
  "end_date": "2026-06-07",
  "entry_count": 3,
  "totals": { "calories": 1000, "protein_g": 50, "carbs_g": 120, "fat_g": 30 },
  "macros": { "carbs_g": 120, "fat_g": 30, "protein_g": 50 },
  "meals": {
    "breakfast": {
      "entry_count": 2,
      "totals": { "calories": 400, "protein_g": 20, "carbs_g": 50, "fat_g": 10 },
      "macros": { "carbs_g": 50, "fat_g": 10, "protein_g": 20 },
      "percent_of_total": { "calories": 40, "carbs_g": 41.67, "fat_g": 33.33, "protein_g": 40 }
    },
    "lunch": {
      "entry_count": 1,
      "totals": { "calories": 600, "protein_g": 30, "carbs_g": 70, "fat_g": 20 },
      "macros": { "carbs_g": 70, "fat_g": 20, "protein_g": 30 },
      "percent_of_total": { "calories": 60, "carbs_g": 58.33, "fat_g": 66.67, "protein_g": 60 }
    }
  }
}

get_target_progress (response_format: "json"):

{
  "start_date": "2026-06-01",
  "end_date": "2026-06-03",
  "calendar_days": 3,
  "days_logged": 2,
  "entry_count": 3,
  "totals": { "calories": 4000, "protein_g": 240, "carbs_g": 480, "fat_g": 140 },
  "macros": { "carbs_g": 480, "fat_g": 140, "protein_g": 240 },
  "average_per_logged_day": { "calories": 2000, "protein_g": 120, "carbs_g": 240, "fat_g": 70 },
  "average_per_calendar_day": { "calories": 1333.33, "protein_g": 80, "carbs_g": 160, "fat_g": 46.67 },
  "targets": { "calories": 2000, "protein_g": 120, "carbs_g": 240, "fat_g": 70 },
  "target_effective_date": "2026-05-20",
  "progress": {
    "calories": {
      "target": 2000,
      "average_per_logged_day": 2000,
      "average_per_calendar_day": 1333.33,
      "logged_day_delta": 0,
      "calendar_day_delta": -666.67,
      "logged_day_percent": 100,
      "calendar_day_percent": 66.67
    }
  }
}

delete_targets (response_format: "json"):

{
  "deleted": true,
  "deleted_count": 1,
  "delete_all": false,
  "effective_date": "2026-06-01",
  "current_targets": null
}

set_targets (response_format: "json"):

{
  "set": true,
  "effective_date": "2026-06-01",
  "targets": { "calories": 2000, "protein_g": 120, "carbs_g": 240, "fat_g": 70 },
  "macros": { "carbs_g": 240, "fat_g": 70, "protein_g": 120 }
}

get_logging_consistency (response_format: "json"):

{
  "start_date": "2026-06-01",
  "end_date": "2026-06-07",
  "calendar_days": 7,
  "days_logged": 5,
  "missed_days": 2,
  "logging_rate_percent": 71.43,
  "total_entries": 9,
  "average_entries_per_logged_day": 1.8,
  "average_entries_per_calendar_day": 1.29,
  "current_streak_days": 2,
  "longest_streak_days": 2,
  "first_logged_date": "2026-06-01",
  "last_logged_date": "2026-06-07",
  "logged_dates": ["2026-06-01", "2026-06-02", "2026-06-04", "2026-06-06", "2026-06-07"],
  "missing_dates": ["2026-06-03", "2026-06-05"]
}

export_entries (format: "csv"):

entry_id,log_date,logged_at,meal,name,brand,quantity,unit,grams,food_ref,notes,calories,protein_g,carbs_g,fat_g,fiber_g,sugar_g,sodium_mg,saturated_fat_g
12,2026-06-11,2026-06-11T19:20:00.000Z,dinner,Thai green curry,,1,serving,450,,home estimate,940,38,68,56,,,,

analyze_meal_photo (log: true, response_format: "json"):

{
  "items": [
    { "name": "Thai green curry", "estimated_grams": 400, "confidence": "medium",
      "nutrients": { "calories": 380, "protein_g": 28, "carbs_g": 18, "fat_g": 24 },
      "macros": { "carbs_g": 18, "fat_g": 24, "protein_g": 28 } }
  ],
  "totals": { "calories": 940, "carbs_g": 68, "fat_g": 56, "protein_g": 38 },
  "macros": { "carbs_g": 68, "fat_g": 56, "protein_g": 38 },
  "notes": "Coconut-milk fat estimated; ...",
  "logged": true,
  "entries": [ { "entry_id": 12, "name": "Thai green curry", "macros": { "...": 0 } } ],
  "day": { "date": "2026-06-11", "totals": { "...": 0 }, "macros": { "...": 0 }, "meals": { "...": {} } }
}

Field stability for analyze_meal_photo:


6. Caveats & limits


7. Quick smoke test

The most reliable check is the TypeScript or Python snippet in §2 — connect and call listTools(); you should see 26 tools.

For a one-liner that just verifies connectivity and your token, this should return HTTP 200 with a valid token and 401 with a bad one:

curl -s -o /dev/null -w "%{http_code}\n" https://foodlog.sig.ai/api/mcp \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

A 401 means the token is missing or not recognized — sign in at /account to create or recover access, or email mcp@sig.ai. (For actual tool calls without an SDK, see Raw JSON-RPCtools/call works without an initialize handshake, but the response is SSE-framed.)