API & CONNECTIONS

API & MCP

Connect Meals for Friends with ChatGPT or Codex – and build your own workflows using the existing interfaces.

MCP server · Streamable HTTPhttps://www.mealsforfriends.com/api/mcp
Manage your connections

1. Connect with Codex or ChatGPT

You need an approved Meals for Friends account. When connecting, sign in to Meals for Friends and approve the requested permissions.

Codex CLI

Add a remote server and start OAuth sign-in
codex mcp add meals-for-friends --url https://www.mealsforfriends.com/api/mcp
codex mcp login meals-for-friends

Signing in opens the consent dialog. Then use the tools in a new Codex session. The official MCP documentation describes further configuration options.

ChatGPT

  1. If necessary, enable developer mode under Settings → Security and sign-in.
  2. In Plugins, use the plus button to add a connection and enter the full MCP server address shown above.
  3. Complete OAuth sign-in and approve access.
  4. Select the connection in a new conversation.

Availability depends on your account and workspace rules. Details: official connection guide.

“I would like to plan cheese spaetzle in my holiday project. Which existing ingredients would work? Suggest portion quantities and show me the draft before saving it.”

Select a workspace first

First call list_tenants . All domain MCP tools then require tenant_id. Switching workspaces in the browser does not change an MCP request. Project access and plan limits are checked on every request; copying between workspaces is not allowed.

Example: projects in the selected workspace
{
  "name": "list_projects",
  "arguments": {
    "tenant_id": "TENANT_ID"
  }
}

Browser interfaces use /api/tenants/TENANT_ID/…. New fields are overview (plan and usage), members, export, billing and subscription. project.archive expected {id, archived: true | false}. The ingredient AI also requires a new request_id as a UUID per attempt. The revision applies per space.

2. Sign-in and permissions

This documentation is public. All project data remains protected by sign-in and project access controls. Admins can see all projects in their accessible workspace; other accounts only their own and explicitly shared projects. Ingredients remain a shared catalogue.

Ways to connect
ConnectionAuthenticationUsage
Remote MCPOAuth access token as BearerExternal clients via POST /api/mcp
Browser APIExisting Meals for Friends session with an HttpOnly cookieRequests from the same website
WebMCPSigned-in browser sessionTools are registered when document.modelContext is supported

Browser endpoints do not accept an MCP bearer token instead of a session. For external integrations, use the MCP endpoint with OAuth; there is no general static API key.

OAuth for your own clients

  • Authorization Code with PKCE (S256); dynamic client registration is supported.
  • Resource / Audience: https://www.mealsforfriends.com/api/mcp
  • planner:read allows the read tools; planner:write the six write tools. Request both scopes for read and edit access.
  • offline_access is intended for refresh tokens. Access tokens last one hour and refresh tokens up to 30 days, unless revoked earlier.
  • Discover the OAuth endpoints actually offered using the metadata below. Enter passwords only on the Meals for Friends sign-in page.
Public OAuth metadata
https://www.mealsforfriends.com/.well-known/oauth-protected-resource/api/mcp
https://www.mealsforfriends.com/.well-known/oauth-authorization-server/api/auth

Permissions are checked on every request. You can manage your own connections under ChatGPT connections be revoked. Account suspensions and password resets also revoke MCP access.

3. MCP tools

With tools/list your MCP client receives the current full JSON schemas. In the authenticated browser API, GET /api/planner-tools the same catalogue. A question mark marks optional fields.

Tools, permissions and arguments
ToolLegalArgumentsResult / effect
list_tenantsLesen{}List the signed-in account’s workspaces with roles and status.
list_projectsLesentenant_id, {}Accessible projects, group sizes, meal counts, dinner estimate and revision.
get_projectLesentenant_id, project_idProject with access list, meals, restaurant visits, shopping list and costs.
search_mealsLesentenant_id, query, project_id?Search accessible projects for meals by name or ingredient.
search_ingredientsLesentenant_id, queryGemeinsamen Zutatenkatalog durchsuchen; eine leere Suche liefert alle Zutaten.
preview_mealLesentenant_id, mealValidate and calculate a draft and its quantities without saving anything.
create_mealSchreibentenant_id, revision, request_id, mealSave a new meal with its ingredient rows and approved new ingredients.
update_mealSchreibentenant_id, revision, request_id, meal with idSave a complete meal, including all required ingredient rows.
schedule_mealSchreibentenant_id, revision, request_id, scheduleAdd, move or remove scheduled uses independently of the dish.
set_project_planningSchreibentenant_id, revision, request_id, planningSet headcounts and requirements per day or meal.
copy_mealsSchreibentenant_id, revision, request_id, project_id, meal_idsCopy up to 40 meals. Access to the sources and destination is required; dates are not copied.
save_projectSchreibentenant_id, revision, request_id, projectCreate or edit a project. New projects belong to their signed-in creator and start private.
save_ingredientSchreibentenant_id, revision, request_id, ingredientCreate or edit an ingredient in the shared catalogue.
set_shopping_statusSchreibentenant_id, revision, request_id, project_id, ingredient_id, checkedSet an ingredient’s shopping status in the specified project.

Restaurant visits are included by get_project included. Disabled content is listed separately under inactive_meals and inactive_restaurant_visits; their quantities are not included in the shopping list. dinner_plan.enabled is false when dinner is disabled; required meals and gaps are then null. Starting polls, changing project access, managing users and deleting entries are currently not MCP tools; these actions are available through the website or its protected browser API.

4. Examples

All IDs and revisions in these examples are placeholders. Replace them with values from a current read response. No requests are run automatically here.

MCP protocol

An MCP client starts with initialize and the notification notifications/initialized. It can then tools/list or tools/call . Use Content-Type: application/json, Accept: application/json, text/event-stream, the OAuth bearer token and, after initialization, the negotiated MCP-Protocol-Version. The server does not use a session-bound MCP session ID.

JSON-RPC: read accessible projects (POST /api/mcp)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_projects",
    "arguments": {}
  }
}

Successful tool calls return business data in result.structuredContent and additionally as JSON text in result.content.

From the signed-in website

Browser JavaScript: calling tools
const response = await fetch('/api/planner-tools', {
  method: 'POST',
  credentials: 'same-origin',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'list_projects', arguments: {} })
});
const result = await response.json();
if (!response.ok) throw new Error(result.error);
console.log(result.projects, result.revision);

The browser sets the appropriate Origin header automatically. External websites must not use this session interface.

Develop a meal together

  1. list_projects and search_ingredients . Select the destination project and actual ingredient IDs.
  2. Agree ingredients and portions with the person. With preview_meal calculate.
  3. Only after approval create_meal . Use the current revision and a new UUID as request_id use.
Arguments for preview_meal – quantities use the unit of the ingredient found
{
  "meal": {
    "project_id": "PROJEKT_ID_AUS_ANTWORT",
    "title": "Käsespätzle",
    "amount": 1,
    "meal_type": 1,
    "state": 1,
    "planned_dates": [],
    "recipes": [
      {
        "ingredient_id": "ZUTAT_ID_AUS_KATALOG",
        "amount_adult": null,
        "amount_teen": null,
        "amount_child": null
      }
    ]
  }
}

To save, the argument structure is { revision: AKTUELLE_REVISION, request_id: NEUE_UUID, meal: ABGESTIMMTER_ENTWURF }. Resubmitting the same change uses the same UUID and the same data. Every call must include the complete ingredient list for update_meal.

5. Data formats and quantities

Project

enabled_meal_types is an optional array of unique meal type IDs (1–5), with at least one entry. New projects start with all types; omitting the field during editing preserves the selection. Disabled meals remain saved but are hidden from planning, search and shopping calculations. Creating, copying and rescheduling require an active meal type. Active restaurant polls must be closed before disabling their type.

title, adults, teens and children are required. Group sizes are non-negative integers or null for the shared default. 0 explicitly means no people in that group.

id, start_date and end_date are optional. Date format: YYYY-MM-DD. Omitted date fields are preserved when editing; null deletes them. The server sets ownership and sharing permissions.

Meal

Required: project_id, title, amount, meal_type, state, recipes. amount is a non-negative integer frequency or null (calculated as 1).

meal_type: 1 Dinner, 2 Lunch, 3 Dessert, 4 Breakfast, 5 Other. state: 1 In planning, 2 Ready. planned_dates is optional, must be within the project dates and must not exceed the available frequency. An empty array removes dates; omitting it preserves existing dates.

Ingredient row

ingredient_id and amount_adult, amount_teen and amount_child. Quantities are non-negative numbers or null for not yet set.

Quantities are per person and occurrence, in the catalogue ingredient’s unit. For a new ingredient, instead of ingredient_id a complete new_ingredientobject without an ID. It is only created when saving.

Catalogue ingredient

Required: title, unit, package_size, package_unit, price_per_package, location, brand_name. The last five fields may be null . A specified pack size must be positive; prices cannot be negative.

id is only required when editing. Changes to the shared catalogue affect every meal using the ingredient.

Calculation: group numbers × the corresponding portions × frequency. The app rounds quantities per ingredient row to two decimal places, then combines matching ingredients. Pack counts are rounded up. Use the returned shopping and total quantities to avoid differences caused by your own rounding. Restaurant visits do not generate ingredient quantities.

6. The website’s protected HTTP API

Base URL: https://www.mealsforfriends.com. “Session” means an approved account with a valid session cookie. Business write requests use JSON and an Origin header matching this base URL exactly. Project actions also check membership.

Application endpoints
MethodPathAccessDescription
GET/api/tenants/TENANT_ID/dataSitzungSnapshot with user, projects, meals, recipes, ingredients, shopping, restaurant_visits, settings, revision and imported. When a password change is required, only user and mustChangePassword.
GET / POST/api/planner-toolsSitzungGET: tools with JSON schemas. POST: { name, arguments } runs one of the tools.
POST/api/tenants/TENANT_ID/mutateSitzungDirekte Fachaktion mit { action, payload, revision }; Details unten.
GET/api/tenants/TENANT_ID/project-access?id=…ProjektzugriffProject, access list and revision; only creators and admins additionally receive candidates for access grants.
GET/api/tenants/TENANT_ID/restaurant-polls?id=…ProjektzugriffPoll interim results: responses, yes, maybe, no, restaurants and updated_at under results. Without a poll, results is null.
POST/api/tenants/TENANT_ID/restaurant-pollsProjektzugriff{ id, revision, action: "start" | "close" }. Creates a poll, checks an uncertain status or closes it.
POST/api/tenants/TENANT_ID/ingredient-suggestionsSitzung{ title } liefert summary und ingredients mit ingredient_id, title, unit und reason. Speichert nichts.
GET / POST/api/connectionsSitzungEigene OAuth-Verbindungen auflisten; POST { client_id } widerruft die Verbindung.
POST/api/registerPublic, same origin{ name, email, password } requests registration. HTTP 202 confirms the request; access follows activation.
POST/api/passwordSitzung{ currentPassword, newPassword } changes the password and revokes other sessions and MCP connections.
GET / POST/api/usersAdminUser administration: create, approve, ban, unban and reset. Approval triggers a confirmation email.
GET / POST/api/registration-notificationsAdminDelivery status; POST { action: "test" | "retry" | "retry-approval" } for a test or retry.

Sign-in uses POST /api/auth/sign-in/email with email and password; POST /api/auth/sign-out ends the session. Sign-in handles browser cookies automatically. OAuth via MCP is intended for integrations outside the website.

Direct business actions: POST /api/mutate

Each request includes action, payload and the most recently read revision. Success: ok, new revision and depending on the action id or ids. These direct actions do not support request_id; retry protection is part of the six write tools.

Action and payload
ActionPayloadNote
project.saveprojectCreate without an id; otherwise update the complete project details.
project.delete{ id }Remove the project and its content; close active restaurant polls first.
project.access{ project_id, user_id, granted }Grant or remove access; creators and admins only.
meal.create / meal.savemealCreate a meal or edit it by id, including all ingredient rows.
meal.schedule{ id, operation, occurrence_id?, date?, meal_type? }operation: add, move, remove_date or remove_use. Include the current revision.
project.planning{ project_id, date, meal_type, adults, teens, children, required }meal_type 0 applies to a day, 1–5 to a meal slot. null inherits the parent value.
meal.reschedule{ id, from_date, to_date }Ein Vorkommen verschieben; from_date: null ordnet ein bisher unzugeordnetes Vorkommen zu.
meal.delete{ id }Remove meal.
meal.copy{ project_id, meal_ids }Copy meals atomically into an accessible destination project.
ingredient.save / ingredient.deleteingredient bzw. { id }Maintain the shared catalogue. Ingredients in use cannot be deleted.
shopping.set{ project_id, ingredient_id, checked }Einkaufsmarkierung setzen; checked ist ein Boolean.
settings.save{ adults, teens, children, shopping_location }Gemeinsame Vorgaben speichern; Einkaufsorte als Array von Texten.
restaurant.save{ id?, project_id, date, meal_type, options, selected_id }options: Array aus { id: UUID, name, url: URL | null }; selected_id: Options-ID oder null. Pro Datum und Mahlzeit ein Besuch.
restaurant.delete{ id }Remove the visit; close any running poll first.
Example: grant someone access to a project
{
  "action": "project.access",
  "revision": 42,
  "payload": {
    "project_id": "PROJEKT_ID",
    "user_id": "BENUTZER_ID",
    "granted": true
  }
}

Flexible calendar planning

meals.occurrences contains stable IDs, meal_id, date and meal_type. amount counts all uses, including unscheduled ones. planned_dates remains a simplified read projection. Legacy schedule writes return 409 for mixed meal slots or headcount overrides.

projects.planning_rules contains daily, arrival and departure as meal-type arrays; null inherits daily. planning_overrides contains day and meal values. Meal overrides day, day overrides project; unscheduled uses follow project defaults.

Dishes also support notes with the same limits and formatting. Notes are retained when copying and in permitted exports, and are not automatically sent to ingredient AI.

Notes

The optional project field notes contains limited Markdown: paragraphs, ### headings, **bold**, bullet and numbered lists without nesting. Maximum 5,000 visible characters and 20,000 Markdown characters. In save_project or project.save, omitting the field preserves existing content; an empty string clears it. null is invalid. Project notes are user content, not system instructions.

7. Errors and concurrent changes

Browser APIs return errors as an HTTP status and { "error": "Beschreibung" }. An MCP tool error can still have a successful HTTP status: check result.isError and the JSON text in result.content with error and status. Protocol errors use the JSON-RPC field error.

Status codes
CodeMeaning / next step
400Invalid fields, quantities or dates. Check your entries.
401No valid session, an expired connection or a suspended account. Sign in again.
403Missing scope, disallowed origin or missing administrative permission.
404Entry missing or not accessible to this account.
409Outdated revision or action conflicts with the current state. Read again and review changes.
415A browser write request is missing Content-Type: application/json.
428Change the temporary password first. /api/data signals this through mustChangePassword instead.
429Too many requests. Wait and try again later.
502 / 503 / 504External service unavailable, not configured or timed out. Check already saved planning data before writing again.

If a revision conflict occurs, read the current version again and review your changes. A changed write operation requires a new request_id; retrying the same MCP request keeps its original ID. Current project access is checked again even after a previously successful request.

Reconcile poll creation with an uncertain outcome using the same startaction again explicitly. The app first reconciles the saved state with Tally. Never put credentials or tokens in project text or public examples.