Skip to main content
Glama
ikeike443
by ikeike443

fatsecret-mcp

CI

Un servidor MCP (Model Context Protocol) remoto personal que permite a Claude buscar en la base de datos de alimentos/recetas de FatSecret y leer/escribir tu propio diario de alimentos, peso y registro de ejercicio directamente en la conversación. Desplegado en el nivel gratuito Hobby de Vercel. Proyecto hermano de fitness-mcp (Hevy): un servidor MCP por producto, compartiendo el mismo patrón de autenticación.

Licencia

MIT

Related MCP server: Nutrition MCP

Estado

  • Búsqueda (Fase 2): implementada — search_foods, get_food_detail, search_recipes, get_recipe_detail, find_food_by_barcode. No se necesita autorización de usuario de FatSecret; solo el Client ID/Secret de OAuth 2.0 de la consola de desarrollador de FatSecret.

  • Diario/peso/ejercicio/perfil (Fase 4): implementada, y parcialmente verificada contra una cuenta real de FatSecret — get_profile, get_food_diary y get_exercise_diary están ahora confirmados en vivo; create_exercise_entry, weight.update y find_food_by_barcode siguen siendo reconstrucciones no verificadas de mejor esfuerzo (consulta "Qué no está verificado" más abajo para el desglose completo).

  • Script de configuración OAuth1 de 3 patas (Fase 3): implementado (scripts/fatsecret-oauth-setup.ts), aún no ejecutado contra una cuenta real de FatSecret.

Dos capas de autenticación

Este servidor se sitúa entre Claude y FatSecret, y cada una de esas dos relaciones se autentica de forma completamente distinta: eso es lo principal que hay que entender antes de tocar el código.

Claude  <──①── this server (fatsecret-mcp)  ──②──>  FatSecret API

① Claude ↔ este servidor — un secreto compartido único, mismo patrón que fitness-mcp. Claude envía Authorization: Bearer <MCP_BEARER_TOKEN> en cada solicitud; lib/auth.ts lo comprueba. Dado que la opción de cabecera estática de Claude sigue estando restringida por beta, este servidor también ejecuta su propio servidor de autorización OAuth 2.1 mínimo (lib/oauth.ts, /api/oauth/authorize, /api/oauth/token) para que los campos estándar de Client ID/Secret de OAuth de Claude funcionen como respaldo siempre disponible — consulta el README de fitness-mcp para el razonamiento completo, que se aplica sin cambios aquí.

Cada fallo en esta capa — un MCP_BEARER_TOKEN incorrecto o ausente, un client_id de OAuth no reconocido, un client_secret incorrecto, PKCE inválido, un redirect_uri no permitido — se registra y, opcionalmente, se alerta en tiempo real; consulta "Registro y alertas de eventos de seguridad" más abajo.

② este servidor ↔ FatSecret — aquí es donde se vuelve más complejo que fitness-mcp, porque FatSecret mismo usa dos versiones diferentes de OAuth para dos tipos diferentes de métodos de API, y no hay forma de evitarlo: así está diseñada la API de FatSecret, no es una elección hecha aquí:

Categoría de método de FatSecret

Métodos de ejemplo

Cómo autentica este servidor

Solicitud firmada (sin usuario específico involucrado)

foods.search, food.get, recipes.search, recipe.get, food.find_id_for_barcode

OAuth 2.0 Credenciales de cliente — lib/fatsecret/appAuth.ts obtiene y almacena en caché un token de portador a nivel de aplicación desde oauth.fatsecret.com. Totalmente automático; sin interacción humana después del registro de desarrollador único.

Solicitud firmada y delegada (lee/escribe tu cuenta de FatSecret)

food_entries.*, food_entry.*, weights.get_month, weight.update, exercise_entries.*, profile.get, foods.get_favorites

OAuth 1.0a, de 3 patas, firmado con HMAC-SHA1 — lib/fatsecret/oauth1.ts. FatSecret no soporta OAuth 2.0 para estos métodos en absoluto, por lo que no hay forma de evitar OAuth1 aquí. Esto requiere una autorización interactiva única (Fase 3, más abajo) donde inicias sesión en FatSecret en un navegador y apruebas esta aplicación; el token/secreto de acceso resultante se reutiliza automáticamente para siempre (consulta la advertencia en la Fase 3).

Concretamente: search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode funcionan en cuanto has registrado una aplicación de FatSecret y has establecido FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET. Todas las demás herramientas necesitan además FATSECRET_CONSUMER_KEY/FATSECRET_CONSUMER_SECRET (OAuth1 — un par de credenciales diferente de la misma aplicación de FatSecret) y FATSECRET_ACCESS_TOKEN/FATSECRET_ACCESS_TOKEN_SECRET (obtenidos ejecutando el script de configuración una vez).

Registro y alertas de eventos de seguridad

Cada comprobación fallida en la capa ① anterior (Claude ↔ este servidor) se informa a través de lib/securityAlert.ts, activando los siguientes puntos:

  • lib/auth.ts (verifyBearerToken) — token de portador ausente, token de portador incorrecto, MCP_BEARER_TOKEN no configurado.

  • /api/oauth/authorize — client_id no reconocido, redirect_uri no permitido (el caso de redirección abierta que isAllowedRedirectUri existe para bloquear), response_type no soportado, desafío PKCE ausente/no S256, OAUTH_CLIENT_SECRET no configurado.

  • /api/oauth/token — client_secret incorrecto, código de autorización inválido/expirado, discrepancia entre código/PKCE/redirect_uri, MCP_BEARER_TOKEN no configurado.

Dos capas independientes, por lo que esto se degrada con elegancia:

  1. Siempre registrado. Cada fallo anterior escribe una línea de JSON estructurado (event, reason, ip, userAgent, path, time) a stderr mediante console.error — sin configuración necesaria, y en Vercel esto aparece en los registros de funciones del despliegue tal cual. El valor real del token de portador / secreto de cliente / verificador PKCE nunca se incluye — solo metadatos sobre el intento fallido — ya que un mecanismo de detección que pudiera filtrar el secreto que está vigilando anularía el propósito; lib/securityAlert.test.ts y lib/auth.test.ts lo verifican directamente.

  2. Alerta opcional en tiempo real. Si SECURITY_ALERT_WEBHOOK_URL está configurado (una URL de "webhook entrante" de Slack o Discord), el mismo evento también se envía allí como un mensaje de una línea, de modo que un intento de intrusión aparece como una notificación push en lugar de ser visible solo cuando alguien abre el visor de registros de Vercel. Un fallo de entrega del webhook (URL expirada, error de red) se registra a su vez como security_alert_delivery_failed, de modo que un webhook roto silenciosamente no se lea como "sin intentos".

El POST del webhook se programa mediante after() de Next para que se ejecute después de que la respuesta ya se haya enviado (sin latencia añadida en la comprobación de autenticación); esto solo funciona dentro de una solicitud real, por lo que se recurre a una llamada simple de disparar y olvidar cuando se invoca directamente (por ejemplo, desde pruebas).

Este es intencionalmente un diseño simple de "alertar en cada fallo", no alertas basadas en umbrales/tasas — consulta los comentarios de documentación de lib/auth.ts/lib/securityAlert.ts para ver qué se excluyó (umbrales basados en recuento, monitorización a nivel de plataforma de Vercel, rotación de credenciales) y por qué.

Herramientas expuestas

Herramienta

Tipo

Autenticación necesaria

Descripción

search_foods

lectura

OAuth2 (aplicación)

Buscar en la base de datos de alimentos de FatSecret por nombre

get_food_detail

lectura

OAuth2 (aplicación)

Nutrición completa por ración para un alimento

search_recipes

lectura

OAuth2 (aplicación)

Buscar en la base de datos de recetas de FatSecret

get_recipe_detail

lectura

OAuth2 (aplicación)

Ingredientes/instrucciones completos para una receta

find_food_by_barcode

lectura

OAuth2 (aplicación)

Resolver un código de barras GTIN-13 a un foodId — necesita el alcance barcode, posiblemente solo Premier

get_food_diary

lectura

OAuth1 (usuario)

Listar entradas del diario de alimentos para una fecha

get_favorite_foods

lectura

OAuth1 (usuario)

Listar alimentos favoritos

get_most_eaten_foods

lectura

OAuth1 (usuario)

Listar alimentos más consumidos, opcionalmente por comida

get_recently_eaten_foods

lectura

OAuth1 (usuario)

Listar alimentos consumidos recientemente, opcionalmente por comida

get_weight_history

lectura

OAuth1 (usuario)

Listar entradas de peso para un mes — posiblemente solo Premier

get_exercise_diary

lectura

OAuth1 (usuario)

Listar entradas de ejercicio para una fecha

get_profile

lectura

OAuth1 (usuario)

Obtener el resumen del perfil de FatSecret del usuario

create_food_diary_entry

escritura

OAuth1 (usuario)

Registrar un alimento en el diario

update_food_diary_entry

escritura

OAuth1 (usuario)

Actualizar una entrada de diario existente

delete_food_diary_entry

escritura

OAuth1 (usuario)

Eliminar una entrada de diario

update_weight

escritura

OAuth1 (usuario)

Registrar/actualizar una entrada de peso — posiblemente solo Premier

create_exercise_entry

escritura

OAuth1 (usuario)

Registrar una entrada de ejercicio

Las herramientas de escritura son de prueba en seco por defecto

Mismo diseño que fitness-mcp: cada herramienta de escritura requiere un argumento confirm: true. Sus descripciones instruyen al LLM que llama a mostrar al usuario exactamente lo que se escribirá y obtener una aprobación explícita primero. Eso es un empujón estructural, no una garantía: el mismo LLM que decide si llamar a la herramienta también establece confirm, y no hay separación de alcance entre herramientas de lectura/escritura en la capa de autenticación, por lo que cualquier llamador que tenga un MCP_BEARER_TOKEN válido puede invocar cualquier herramienta.

Qué no está verificado

No existía ningún registro de API de FatSecret cuando se construyó este proyecto por primera vez, por lo que la mayor parte comenzó como reconstrucciones de mejor esfuerzo. Desde entonces se ha comprobado contra una cuenta real para algunas herramientas — estado a continuación:

  • Confirmado en vivo, coincide exactamente con la implementación: search_foods (foods.search), get_food_diary (food_entries.get, incluyendo la capitalización real del campo meal, p. ej. "Breakfast").

  • Confirmado en vivo, corregido tras la comprobación: get_profile (profile.get) — una respuesta real incluía height_cm, que aún no estaba expuesto como campo; ahora añadido.

  • Confirmado en vivo, la forma real es más compleja de lo asumido: get_exercise_diary (exercise_entries.get). El método/envoltorio son reales, pero una entrada real sincronizada desde una app de salud conectada ({exercise_id: "184", exercise_name: "Google Health Connect", minutes: "1440", calories: "1655"} — la actividad agregada de un día completo, no un entrenamiento individual) no tiene exercise_entry_id ni date_int en absoluto. lib/fatsecret/exercise.ts ahora lo gestiona de forma defensiva (los campos ausentes se convierten en null, no en un fallo ni en un valor fabricado engañoso) y conserva la entrada bruta completa bajo raw. Sigue abierto: si un ejercicio registrado manualmente (mediante la app de FatSecret) tiene un id/fecha como las entradas de food_entries.get — sin probar.

  • Aún sin verificar / reconstrucciones de buena fe: la forma de la respuesta de food.find_id_for_barcode, los nombres de parámetros de weight.update, y el nombre del método y los parámetros de create_exercise_entry (el hallazgo del diario de ejercicio anterior implica que su supuesto modelo de datos de "entrada individual creable" puede no sostenerse — ver la advertencia en lib/fatsecret/exercise.ts). Trátalos como un punto de partida, no como verdad verificada.

  • Ejecuta la lista de verificación de verificación manual que aparece más abajo contra una cuenta real para cualquiera de los dos puntos anteriores, y corrige cualquier discrepancia que encuentres (las pruebas unitarias en lib/fatsecret/*.test.ts necesitarán actualizaciones correspondientes).

Configuración

  1. Registra una app de FatSecret Platform API en https://platform.fatsecret.com/. Obtendrás:

    • Un Client ID/Secret de OAuth 2.0 (para FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET).

    • Un Consumer Key/Secret de OAuth 1.0 (para FATSECRET_CONSUMER_KEY/FATSECRET_CONSUMER_SECRET) — un par separado de la misma app, no el mismo que las credenciales OAuth2 anteriores.

    • Comprueba qué ámbitos (scopes) incluye tu plan (basic / premier / barcode / ...) — se informa que weights.get_month/weight.update/find_food_by_barcode requieren Premier o los ámbitos barcode/premier; confírmalo con tu propio plan y ajusta FATSECRET_OAUTH2_SCOPE si es necesario.

    • Añade a la lista blanca tu(s) IP(s) de salida (hasta 15 direcciones/rangos) — la restricción de IP de FatSecret no se limita al endpoint de token: confirmado contra un despliegue real de Vercel que la propia llamada a la API foods.search fue rechazada (código de error 21, "Invalid IP address detected") desde una IP no incluida en la lista blanca, incluso con un token emitido válidamente. Así que tanto la obtención única del token OAuth2 como cada llamada individual de búsqueda/detalle deben originarse desde una IP en la lista blanca. Localmente esto es simplemente la IP pública de tu propia máquina (curl https://ifconfig.me). En Vercel, cuyas funciones serverless no tienen una IP de salida fija por defecto, consulta "Fixed outbound IP for Vercel" más abajo — necesario antes de que cualquier herramienta de Signed Request funcione en producción.

  2. Ejecuta el servidor de desarrollo local una vez para hacer una prueba rápida de búsqueda (la Fase 2 solo necesita el paso 1):

    npm install
    cp .env.example .env.local   # fill in FATSECRET_CLIENT_ID/SECRET + the MCP_BEARER_TOKEN/OAuth trio
    vercel dev
  3. Ejecuta la configuración OAuth1 de tres patas (3-legged) una sola vez (necesaria para todas las herramientas excepto las 5 de búsqueda/detalle) — ver Fase 3 más abajo.

  4. Despliega en Vercel — ver Despliegue más abajo, pero lee primero "Fixed outbound IP for Vercel".

Fixed outbound IP for Vercel

Las funciones serverless de Vercel no tienen una IP de salida fija, lo cual es un problema dado el hallazgo anterior — cada llamada a search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode, no solo la obtención del token, debe provenir de una IP en la lista blanca. Sin esto, esas cinco herramientas funcionan bien localmente (tu IP de máquina es la que añadiste a la lista blanca) pero fallan en producción con FatSecret API error 21: Invalid IP address detected.

Solución: enruta esas peticiones a través de un proxy HTTP con IP fija. Este servidor soporta Fixie de serie:

  1. Regístrate en usefixie.com — el plan gratuito tricycleFree (500 peticiones/100MB al mes, $0) es suficiente para uso personal, ya que esto solo transporta el tráfico de Signed Request de FatSecret, no toda tu app. Ten en cuenta que la cuota de peticiones del plan es una restricción real, a diferencia de un límite de tasa solo de app — si buscas mucho, vigila el uso y mejora de plan (commuter, $5/mes/2.500 peticiones) si te acercas al límite.

  2. Copia la URL del proxy que Fixie te da (http://fixie:<password>@<host>:<port>).

  3. Establécelo como FIXIE_URL — en .env.local para pruebas locales contra el proxy, y como variable de entorno de Vercel para producción. Déjalo sin establecer para el desarrollo local ordinario (donde tu propia IP ya está directamente en la lista blanca) — lib/fatsecret/appAuth.ts solo enruta a través del proxy cuando FIXIE_URL está presente.

  4. Añade la IP fija de Fixie (mostrada en tu panel de Fixie) a la lista blanca en la consola de desarrollador de FatSecret, además de (no en lugar de) cualquier IP que hayas añadido para el desarrollo local.

Ningún otro tráfico servidor-a-FatSecret pasa por este proxy — las peticiones OAuth1 (Signed & Delegated) en lib/fatsecret/oauth1.ts no tienen restricción de IP, así que las herramientas de diario/peso/ejercicio/perfil no necesitan FIXIE_URL en absoluto.

Desarrollo local

npm install
cp .env.example .env.local   # fill in real values
vercel dev

Prueba rápida (sustituye $MCP_BEARER_TOKEN):

curl -X POST http://localhost:3000/api/mcp \
  -H "Authorization: Bearer $MCP_BEARER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Debería devolver las 17 herramientas anteriores. Una petición con un token ausente/incorrecto debería obtener 401.

Fase 3: configuración OAuth1 de tres patas (3-legged) única

Todas las herramientas excepto search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode necesitan un token/secret de acceso OAuth1 vinculado a tu cuenta de FatSecret. Obténlo una vez:

npm run fatsecret:oauth-setup

Esto (scripts/fatsecret-oauth-setup.ts) hará lo siguiente:

  1. Solicitará un token de petición no autorizado a FatSecret.

  2. Imprimirá una URL de autorización — ábrela, inicia sesión en FatSecret y aprueba. FatSecret muestra un código de confirmación.

  3. Te pedirá que pegues ese código, y luego lo intercambiará por un token/secret de acceso permanente.

  4. Escribirá FATSECRET_ACCESS_TOKEN/FATSECRET_ACCESS_TOKEN_SECRET en .env.local.

Luego añade también esos mismos dos valores a las variables de entorno de Vercel (.env.local nunca se despliega) — ver Despliegue más abajo.

Según la documentación de FatSecret, este token de acceso no caduca. Si alguna vez se revoca (p. ej. eliminas el acceso de la app desde la configuración de tu cuenta de FatSecret), simplemente vuelve a ejecutar el script para obtener uno nuevo — véase el patrón derive() de fitness-mcp en espíritu: perder una credencial aquí no es un desastre, es una corrección de un comando, solo que esta vez interactiva en lugar de una re-derivación determinista.

Generación de los secretos orientados a Claude a partir de una única frase de contraseña memorable

MCP_BEARER_TOKEN, OAUTH_CLIENT_ID y OAUTH_CLIENT_SECRET (capa ① — Claude ↔ este servidor, sin relación con las credenciales de FatSecret anteriores) pueden derivarse todos de forma determinista a partir de una única frase de contraseña maestra, así que perder los valores almacenados no es un desastre — solo hay que re-derivarlos:

derive() {
  if [ -z "$MASTER_PASSPHRASE" ]; then
    printf "Master passphrase: "
    read -rs MASTER_PASSPHRASE
    echo
  fi
  echo -n "$1" | openssl dgst -sha256 -hmac "$MASTER_PASSPHRASE" -hex | awk '{print $2}'
}

derive "fatsecret-mcp:bearer-token"        # → MCP_BEARER_TOKEN
derive "fatsecret-mcp:oauth-client-id"     # → OAUTH_CLIENT_ID
derive "fatsecret-mcp:oauth-client-secret" # → OAUTH_CLIENT_SECRET

Las cadenas de etiqueta no son secretas (es seguro mantenerlas en este README) — solo la frase de contraseña lo es. Ejecutar derive de nuevo con la misma frase de contraseña siempre reproduce los mismos valores. Esto no se aplica a las credenciales del lado de FatSecret (FATSECRET_CLIENT_ID/SECRET, FATSECRET_CONSUMER_KEY/SECRET, FATSECRET_ACCESS_TOKEN/SECRET) — esas provienen de la consola de desarrollador de FatSecret y del script de configuración OAuth1, no de esta frase de contraseña.

Pruebas

Tres capas, todas ejecutadas en CI (.github/workflows/ci.yml) en cada push/PR — ninguna requiere secretos reales de FatSecret, así que funcionan igual en un repositorio público:

npm run test        # unit + integration (vitest) — pure logic, plus the real Next.js
                     # route handler exercised with fetch mocked
npm run build
npm run test:e2e     # starts a real `next start` server and hits it over real HTTP
                      # (node's built-in test runner, no extra dependency)
  • Unitarias (lib/**/*.test.ts): verificación del bearer token, firma de código OAuth2.1/PKCE/lista blanca de redirect-URI (vector de prueba RFC 7636 incluido), obtención/caché/refresco de token de Client Credentials OAuth2 de FatSecret (lib/fatsecret/appAuth.test.ts), firma HMAC-SHA1 OAuth1 contrastada con una reimplementación independiente (lib/fatsecret/oauth1.test.ts), y la normalización de la forma de respuesta de cada lib/fatsecret/*.ts (objeto-único-vs-array, cadena-numérica-vs-número, peculiaridades de respuesta vacía).

  • De integración (test/integration/*.test.ts): el manejador real app/api/mcp/route.ts conectado a los módulos reales lib/fatsecret/* con solo fetch simulado, cubriendo tanto las rutas de herramientas OAuth2 (Signed Request) como OAuth1 (Signed & Delegated), y el bloqueo por confirmación en cada herramienta de escritura; las rutas reales /api/oauth/authorize//api/oauth/token; las rutas de metadatos OAuth .well-known.

  • E2E (test/e2e/*.e2e.test.mjs): arranca la compilación de producción y verifica sobre HTTP real — comprobación de salud, 401 en autenticación incorrecta/ausente, tools/list devuelve las 17 herramientas, metadatos de descubrimiento OAuth, y un recorrido completo de código de autorización + PKCE. No ejercita datos reales de FatSecret (CI no tiene credenciales reales por diseño).

Verificación manual contra una cuenta real de FatSecret

CI nunca toca datos reales de FatSecret y — según "What's unverified" más arriba — algunas suposiciones de este servidor sobre las formas exactas de respuesta de FatSecret no se han comprobado en absoluto contra una cuenta real. Después de registrarte y ejecutar el script de configuración OAuth1, recorre esta lista de verificación y corrige cualquier discrepancia que encuentres:

  1. Establece FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET reales en .env.local, ejecuta vercel dev y llama a search_foods con una consulta real — hecho, confirmado que funciona contra una cuenta real. Aún así, haz esto para get_food_detail si aún no lo has hecho — confirma que devuelve números de nutrición razonables.

  2. Llama a search_recipes y get_recipe_detail de forma similar. Sigue abierto.

  3. Si tu plan incluye el ámbito barcode, llama a find_food_by_barcode con el código de barras de un producto real y confirma que la forma de la respuesta coincide con RawFindIdForBarcodeResponse de lib/fatsecret/foods.ts — corrígelo si no es así. Sigue abierto.

  4. Ejecuta npm run fatsecret:oauth-setup, y luego llama a get_profile y get_food_diary — hecho. get_food_diary coincidió exactamente; a get_profile le faltaba heightCm, ahora corregido — ver "What's unverified" más arriba.

  5. Llama a create_food_diary_entry con confirm: true y una entrada obviamente desechable, luego get_food_diary para la misma fecha y confirma que aparece con la comida/ración/cantidad/comida correctas. Luego update_food_diary_entry y delete_food_diary_entry — confirma que cada una hace el recorrido completo. Sigue abierto — ten en cuenta que meal vuelve capitalizado ("Breakfast") desde get_food_diary; vale la pena verificar dos veces que create_food_diary_entry/update_food_diary_entry aceptan esa misma capitalización al escribir (o la capitalización que el lado de escritura de FatSecret realmente espere) antes de asumir que está bien.

  6. Si tu plan incluye el seguimiento de peso, llama a update_weight con confirm: true y confirma que get_weight_history lo refleja. Sigue abierto.

  7. create_exercise_entry y get_exercise_diary son el par menos verificado de este código. El método/envoltorio de get_exercise_diary ahora está confirmado como real, pero reveló que el modelo de datos del diario de ejercicio es más complejo de lo asumido (ver "What's unverified" más arriba) — antes de confiar en create_exercise_entry, registra un ejercicio manualmente en la app de FatSecret primero y vuelve a comprobar get_exercise_diary para ver si una entrada manual tiene un exercise_entry_id/date_int como las entradas de comida; eso te dirá si "entrada individual creable" es siquiera el modelo correcto aquí, antes de probar create_exercise_entry en sí contra datos reales.

  8. Nunca confirmes credenciales reales de FatSecret, y nunca ejecutes esta lista de verificación en CI.

Variables de entorno

Variable

Propósito

FATSECRET_CLIENT_ID / FATSECRET_CLIENT_SECRET

Credenciales de cliente OAuth 2.0 — métodos de Signed Request (herramientas de búsqueda/detalle)

FATSECRET_OAUTH2_SCOPE

Opcional. Ámbito(s) OAuth2 separados por espacios, por defecto basic. Añade barcode/premier según sea necesario

FATSECRET_FOOD_GET_METHOD

Opcional. Por defecto food.get.v4; sobrescríbelo (p. ej. food.get) si tu plan no incluye acceso a v4

FIXIE_URL

Opcional. URL del proxy HTTP de IP fija (http://fixie:<password>@<host>:<port>) para la obtención del token OAuth2 y cada llamada de Signed Request — obligatorio en Vercel, ya que no tiene IP de salida fija por defecto. Consulta «Fixed outbound IP for Vercel» más arriba. Déjalo sin definir para desarrollo local.

FATSECRET_CONSUMER_KEY / FATSECRET_CONSUMER_SECRET

Clave/Secreto de consumidor OAuth 1.0 — firma tanto el script de configuración único como cada llamada Signed y Delegated

FATSECRET_ACCESS_TOKEN / FATSECRET_ACCESS_TOKEN_SECRET

Token/secreto de acceso OAuth 1.0 para tu cuenta de FatSecret — se obtiene mediante npm run fatsecret:oauth-setup (Fase 3)

MCP_BEARER_TOKEN

Secreto compartido que este servidor exige en cada petición, y el access_token que emite nuestro flujo OAuth

OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET

Credenciales para el servidor de autorización OAuth mínimo propio de este servidor

OAUTH_ALLOWED_REDIRECT_HOSTS

Opcional. Lista de permitidos separada por comas para el redirect_uri de /api/oauth/authorize. Por defecto claude.ai,claude.com

SECURITY_ALERT_WEBHOOK_URL

Opcional. URL de webhook entrante de Slack/Discord para alertas en tiempo real sobre fallos de autenticación — consulta «Security event logging & alerting» más arriba. Los fallos siempre se registran en stderr tanto si está definido como si no

Defínelas en las Variables de Entorno del proyecto de Vercel (Production + Preview). Nunca subas valores reales al repositorio — .env.example solo documenta los nombres.

Despliegue

  1. vercel link

  2. vercel env add FATSECRET_CLIENT_ID (repítelo para cada variable de la tabla anterior de la que tengas un valor — como mínimo FATSECRET_CLIENT_ID/SECRET, MCP_BEARER_TOKEN, OAUTH_CLIENT_ID/SECRET; añade FIXIE_URL según «Fixed outbound IP for Vercel» más arriba — obligatorio, no opcional, en la práctica; añade el par FATSECRET_CONSUMER_*/FATSECRET_ACCESS_TOKEN* una vez que hayas ejecutado el script de configuración OAuth1)

  3. Configura la versión de Node.js del proyecto de Vercel a 22.19 o superior (Project → Settings → General → Node.js Version, o donde lo ponga el panel actual de Vercel) antes de desplegar — es decir, antes del paso 4 siguiente. La dependencia undici@8 de este servidor (usada para el proxy Fixie — consulta «Fixed outbound IP for Vercel» más arriba) declara "engines": {"node": ">=22.19.0"}, y el propio campo engines de package.json documenta el mismo requisito — pero ninguno de los dos impone nada por sí solo en Vercel, así que un proyecto fijado a una versión anterior de Node (p. ej. 20.x) se desplegará «correctamente» y luego fallará en tiempo de ejecución.

  4. Conecta este repositorio de GitHub en el panel de Vercel para el despliegue automático al hacer push a main, o ejecuta vercel --prod manualmente.

  5. Anota la URL desplegada (consulta Project → Settings → Domains — la URL de producción de este proyecto resultó ser el no reclamado https://fatsecret-mcp.vercel.app, pero es el espacio de nombres compartido de Vercel, así que no des por hecho que estará libre para un fork).

  6. Añade la IP fija de Fixie a la lista de permitidos en la consola de desarrollador de FatSecret (consulta «Fixed outbound IP for Vercel» más arriba) — es el paso que más probablemente cause problemas en producción, ya que sin él search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode fallan todos con FatSecret API error 21.

Conectar con Claude

Los conectores personalizados solo se pueden añadir desde claude.ai (web) o la aplicación de escritorio — no desde la aplicación móvil. Una vez añadidos allí, se pueden usar desde el móvil automáticamente.

  1. En claude.ai: Settings → Connectors → Add custom connector.

  2. Nombre: FatSecret. URL: https://<your-deployment>/api/mcp.

  3. Si tu cuenta tiene la beta «Request headers»: añade Authorization: Bearer <MCP_BEARER_TOKEN> allí y salta al paso 5.

  4. De lo contrario, abre Advanced settings y rellena OAuth Client ID / OAuth Client Secret con los valores OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET definidos en Vercel. Claude descubrirá los endpoints /authorize y /token automáticamente mediante los metadatos .well-known de este servidor.

  5. Guarda. Claude debería listar las 17 herramientas anteriores.

Prueba a preguntar: «バナナのカロリーを教えて» (dime las calorías de un plátano), o «今日の朝食にバナナを1本記録して» (registra un plátano para el desayuno de hoy — una vez que las Fases 3/4 estén configuradas y verificadas).

Agradecimientos

El diseño del flujo OAuth1 de 3 patas se basó en fcoury/fatsecret-mcp (MIT), que expone el flujo OAuth como herramientas MCP en sí mismas; este proyecto, en cambio, lo ejecuta una sola vez como script de configuración independiente (scripts/fatsecret-oauth-setup.ts), ya que está pensado para una única cuenta personal de FatSecret en lugar de uso multiusuario. No se copió ningún código de él.

Related MCP Connectors

Related MCP Servers