Mealie MCP Server
by rorymcdaniel
README.md
# Mealie MCP Server
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org/)
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that connects Claude and other MCP clients to your [Mealie](https://mealie.io) recipe manager instance. Search, create, import, and update recipes through natural language.
## Features
- **Get Recipe** — Retrieve full recipe details by slug or ID
- **Search Recipes** — Free-text search with filtering by category, tag, food, and tool
- **Create Recipe** — Create recipes from structured data (name, ingredients, instructions, etc.)
- **Import Recipe** — Import recipes by URL (Mealie scrapes the page automatically)
- **Update Recipe** — Modify any field on an existing recipe
## Prerequisites
- [Mealie](https://mealie.io) v1+ instance running and accessible
- A Mealie API token (generate at `<your-mealie-url>/user/profile/api-tokens`)
- Node.js 20+ (for local usage) or Docker
## Transports
The server supports two transports:
| Transport | Use case | Entry point |
|---|---|---|
| **stdio** (default) | Claude Desktop, Claude Code, local MCP clients | `build/index.js` |
| **Streamable HTTP** | Claude.ai web/mobile, remote access, multi-user | `build/http.js` |
## Authentication
There are two separate auth layers — one for the server to talk to Mealie, and one for clients to talk to the server:
```
Claude.ai ──OAuth 2.0──▶ MCP Server ──API Token──▶ Mealie
```
1. **Mealie API Token** (`MEALIE_API_TOKEN`) — The server's backend credential for accessing Mealie's REST API. Required for both transports. Generate one at `<your-mealie-url>/user/profile/api-tokens`.
2. **OAuth 2.0 + PKCE** (HTTP transport only) — How remote clients like Claude.ai authenticate to the MCP server. Users log in with their **Mealie username and password** via a browser-based flow. The server validates credentials against Mealie and issues OAuth tokens. Not used with stdio transport.
In short: the API token lets the server access recipes, and OAuth lets users prove who they are.
## Configuration
Copy `.env.example` to `.env` and fill in your values:
```bash
cp .env.example .env
```
### Environment variables
| Variable | Required | Description |
|---|---|---|
| `MEALIE_URL` | Yes | Base URL of your Mealie instance (e.g. `http://localhost:9925`) |
| `MEALIE_API_TOKEN` | Yes | Your Mealie API token |
| `MCP_SERVER_URL` | HTTP mode only | Public URL of this server (e.g. `https://mcp.your-domain.com`) — used as the OAuth issuer URL |
| `PORT` | No | HTTP server port (default: `3000`, HTTP mode only) |
| `TRANSPORT` | No | Set to `http` for HTTP mode in Docker (default: `stdio`) |
## Usage
### With Claude Desktop (stdio)
Add to your `claude_desktop_config.json`:
**Using Docker:**
```json
{
"mcpServers": {
"mealie": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MEALIE_URL",
"-e", "MEALIE_API_TOKEN",
"mealie-mcp-server"
],
"env": {
"MEALIE_URL": "http://your-mealie-instance:9925",
"MEALIE_API_TOKEN": "your-api-token"
}
}
}
}
```
**Using Node.js directly:**
```json
{
"mcpServers": {
"mealie": {
"command": "node",
"args": ["/path/to/mealie-mcp-server/build/index.js"],
"env": {
"MEALIE_URL": "http://your-mealie-instance:9925",
"MEALIE_API_TOKEN": "your-api-token"
}
}
}
}
```
### With Claude Code (stdio)
```bash
claude mcp add mealie -- node /path/to/mealie-mcp-server/build/index.js \
-e MEALIE_URL=http://your-mealie-instance:9925 \
-e MEALIE_API_TOKEN=your-api-token
```
### HTTP Transport (Claude.ai web / remote access)
The HTTP transport uses **OAuth 2.0 with PKCE**, with Mealie credentials as the identity provider. Users authenticate via a login page using their Mealie username and password. This makes it compatible with Claude.ai (web and mobile), which requires OAuth-capable MCP servers.
**Requirements:** The server must be reachable at a public HTTPS URL (`MCP_SERVER_URL`).
**Using Node.js:**
```bash
MEALIE_URL=http://your-mealie:9925 \
MEALIE_API_TOKEN=your-api-token \
MCP_SERVER_URL=https://mcp.your-domain.com \
node build/http.js
```
**Using Docker:**
```bash
docker run --rm \
-p 3000:3000 \
-e TRANSPORT=http \
-e MEALIE_URL=http://your-mealie:9925 \
-e MEALIE_API_TOKEN=your-api-token \
-e MCP_SERVER_URL=https://mcp.your-domain.com \
mealie-mcp-server
```
**Using Docker Compose:**
```bash
# Set env vars in .env file, then:
docker compose up mealie-mcp-server-http
```
**Connecting Claude.ai to the HTTP endpoint:**
In Claude.ai settings, add a new MCP server pointing at `https://mcp.your-domain.com/mcp`. Claude.ai will discover the OAuth metadata automatically and prompt you to log in with your Mealie credentials.
**Nginx configuration:**
See [`nginx-mcp.conf.example`](./nginx-mcp.conf.example) for the location blocks needed to proxy MCP and OAuth traffic when running behind nginx.
A health check endpoint is available at `GET /health`.
**Note on auth state:** The HTTP server stores OAuth tokens in memory. Restarting the server invalidates all existing sessions and requires clients to re-authenticate.
## Tools
### `get_recipe`
Get a recipe by its slug or ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `slug` | string | Yes | Recipe slug or UUID |
### `search_recipes`
Search and list recipes with optional filters.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `search` | string | No | Free-text search query |
| `categories` | string[] | No | Filter by category slugs or IDs |
| `tags` | string[] | No | Filter by tag slugs or IDs |
| `foods` | string[] | No | Filter by food slugs or IDs |
| `tools` | string[] | No | Filter by tool slugs or IDs |
| `requireAllCategories` | boolean | No | Require all specified categories |
| `requireAllTags` | boolean | No | Require all specified tags |
| `page` | number | No | Page number (default: 1) |
| `perPage` | number | No | Results per page (default: 50) |
| `orderBy` | string | No | Sort field (e.g. `createdAt`, `name`) |
| `orderDirection` | `asc` \| `desc` | No | Sort direction |
### `create_recipe`
Create a recipe from structured data.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Recipe name |
| `description` | string | No | Short description |
| `recipeYield` | string | No | Yield (e.g. "4 servings") |
| `prepTime` | string | No | Prep time |
| `cookTime` | string | No | Cook time |
| `totalTime` | string | No | Total time |
| `recipeIngredient` | object[] | No | Ingredients list |
| `recipeInstructions` | object[] | No | Instruction steps |
| `nutrition` | object | No | Nutritional info |
| `recipeCategory` | object[] | No | Categories |
| `tags` | object[] | No | Tags |
| `notes` | object[] | No | Recipe notes |
### `import_recipe`
Import a recipe by URL. Mealie scrapes the page and extracts recipe data.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `url` | string | Yes | URL of the recipe page |
| `includeTags` | boolean | No | Extract tags from the page |
| `includeCategories` | boolean | No | Extract categories from the page |
### `update_recipe`
Update fields on an existing recipe. Only provided fields are changed.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `slug` | string | Yes | Recipe slug or UUID to update |
| `name` | string | No | New name |
| `description` | string | No | New description |
| `recipeYield` | string | No | New yield |
| `prepTime` | string | No | New prep time |
| `cookTime` | string | No | New cook time |
| `recipeIngredient` | object[] | No | Replacement ingredients |
| `recipeInstructions` | object[] | No | Replacement instructions |
| `nutrition` | object | No | Nutritional info |
| `recipeCategory` | object[] | No | Categories |
| `tags` | object[] | No | Tags |
| `notes` | object[] | No | Notes |
## Development
### Setup
```bash
npm install
```
### Build
```bash
npm run build
```
### Run in development
```bash
# stdio transport
MEALIE_URL=http://localhost:9925 MEALIE_API_TOKEN=your-token npm run dev
# HTTP transport
MEALIE_URL=http://localhost:9925 MEALIE_API_TOKEN=your-token \
MCP_SERVER_URL=http://localhost:3000 npm run dev:http
```
### Test
```bash
npm test
# With coverage
npm run test:coverage
# Watch mode
npm run test:watch
```
### Docker
```bash
# Build the image
docker build -t mealie-mcp-server .
# Run (stdio)
docker run -i --rm \
-e MEALIE_URL=http://your-mealie:9925 \
-e MEALIE_API_TOKEN=your-token \
mealie-mcp-server
# Run (HTTP with OAuth)
docker run --rm -p 3000:3000 \
-e TRANSPORT=http \
-e MEALIE_URL=http://your-mealie:9925 \
-e MEALIE_API_TOKEN=your-token \
-e MCP_SERVER_URL=https://mcp.your-domain.com \
mealie-mcp-server
```
## Architecture
```
src/
├── index.ts # stdio entry point
├── http.ts # HTTP entry point (Streamable HTTP transport + OAuth)
├── server.ts # Creates MCP server and registers all tools (transport-agnostic)
├── mealie-client.ts # HTTP client for the Mealie REST API
├── types.ts # TypeScript type definitions for Mealie API shapes
├── auth/
│ ├── provider.ts # OAuth 2.0 provider (validates Mealie credentials, issues tokens)
│ └── login-page.ts # HTML login page rendered during OAuth authorization flow
└── tools/
├── get-recipe.ts # get_recipe tool
├── search-recipes.ts # search_recipes tool
├── create-recipe.ts # create_recipe tool
├── import-recipe.ts # import_recipe tool
└── update-recipe.ts # update_recipe tool
```
The server core (`server.ts`) is **transport-agnostic** — it builds the MCP server and registers tools without knowing which transport will be used. The two entry points (`index.ts` for stdio, `http.ts` for HTTP) each wire in their respective transport.
- **stdio** communicates via stdin/stdout using JSON-RPC 2.0. Standard for Claude Desktop and local MCP clients.
- **Streamable HTTP** exposes a `/mcp` endpoint supporting POST (requests), GET (SSE stream), and DELETE (session termination). Uses stateful sessions with `Mcp-Session-Id` headers. Full OAuth 2.0 + PKCE authentication using Mealie credentials as the identity provider.
## License
MIT
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues