Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Cocktail Recipes

БесплатноНе проверен

A Dockerized Python MCP server that provides tools to manage cocktail recipes, ingredients, and related operations via the Aephir/cocktail-recipes REST API, wit

GitHubEmbed

Описание

A Dockerized Python MCP server that provides tools to manage cocktail recipes, ingredients, and related operations via the Aephir/cocktail-recipes REST API, with dry-run safety defaults and automatic re-authentication.

README

Dockerized Python 3.11 MCP server for the Aephir/cocktail-recipes REST API.

This server is designed for Claude Desktop and other MCP clients. It authenticates against the cocktail app via session-cookie login (POST /api/auth/login) and uses REST-only calls.

Features

  • Python 3.11 MCP server with typed models and validation
  • Self-contained OAuth 2.1 authorization server for remote Claude connectors
  • Session-cookie auth with automatic re-login on 401
  • Safe retries for idempotent calls (read-only and dry-run operations)
  • No blind retries for non-idempotent apply calls
  • Dry-run safety defaults for destructive tools
  • Structured response and error schema
  • Capability probing tool for backend endpoint readiness
  • Operation log tool for recent MCP actions

Implemented Tools

  • list_recipes
  • get_recipe
  • create_recipe (defaults dry_run=true)
  • update_recipe (defaults dry_run=true)
  • delete_recipe (defaults dry_run=true)
  • list_ingredients
  • create_ingredient (defaults dry_run=true)
  • update_ingredient (defaults dry_run=true)
  • delete_ingredient (defaults dry_run=true, supports force=true)
  • list_tools
  • create_tool (defaults dry_run=true)
  • update_tool (defaults dry_run=true)
  • delete_tool (defaults dry_run=true, supports force=true)
  • merge_ingredients (dry_run=true default)
  • merge_tools (dry_run=true default)
  • recategorize_recipes (dry_run=true default)
  • bulk_update_recipes (dry_run=true default)
  • update_tags_bulk (dry_run=true default)
  • operation_log_recent
  • api_capabilities

Safety Behavior

All destructive tools default to dry_run=true (create/update/delete/merge/recategorize/tag bulk).

To apply, pass explicit dry_run=false.

Each destructive tool returns:

  • preview (affected IDs and count)
  • apply_executed boolean
  • backend result payload

Apply integrity guard:

  • If a tool is called with dry_run=false but the backend responds with dry_run=true, MCP returns an error (apply_not_executed) instead of success.
  • If the backend explicitly reports zero changed rows/ids for an apply request, MCP returns an error (apply_not_executed) so silent no-ops are surfaced.

If backend admin endpoints are not available yet, tool responses return structured not_implemented with an actionable message.

Recipe update safety:

  • update_recipe now uses fetch-merge-send behavior on apply: MCP first reads the current recipe, merges your provided fields, and sends a full update payload.
  • This prevents accidental clearing of tools or garnishes when those fields are omitted from the request.
  • To intentionally clear a list field, pass it explicitly as an empty list.

Delete safeguards for ingredients/tools:

  • delete_ingredient and delete_tool perform a recipe-reference precheck.
  • If references are found, apply mode is blocked unless force=true.
  • Dry-run output includes reference count and sample impacted recipes for review.

Tool Permission Hints

This server publishes MCP tool annotations so clients can distinguish read-only tools from mutating tools for permission workflows.

  • Read-only tools (readOnlyHint=true): list_recipes, get_recipe, list_ingredients, list_tools, operation_log_recent, api_capabilities
  • Additive write tools (readOnlyHint=false, destructiveHint=false): create_recipe, create_ingredient, create_tool
  • Mutating/destructive tools (readOnlyHint=false, destructiveHint=true): update_recipe, delete_recipe, update_ingredient, delete_ingredient, update_tool, delete_tool, merge_ingredients, merge_tools, recategorize_recipes, bulk_update_recipes, update_tags_bulk

In clients that support bulk approvals, this enables "always allow" for the read-only set while keeping mutating tools gated.

Required Environment Variables

  • COCKTAIL_API_BASE_URL
  • COCKTAIL_API_USERNAME
  • COCKTAIL_API_PASSWORD
  • PUBLIC_BASE_URL when using streamable-http
  • AUTH_USERNAME when using streamable-http
  • AUTH_PASSWORD when using streamable-http
  • OAUTH_STORAGE_HOST_DIR when using a host-path mount for OAuth state

Optional variables are in .env.example.

Transport Modes

  • stdio (default in .env.example): process-local MCP over stdin/stdout, no published port.
  • streamable-http: network-reachable MCP over HTTP with embedded OAuth 2.1, requires a published port or reverse proxy.

For Claude running on a different machine, use streamable-http.

Embedded OAuth

When MCP_TRANSPORT=streamable-http, this container acts as both:

  • the OAuth authorization server
  • the OAuth-protected MCP resource server

Implemented endpoints:

  • GET /.well-known/oauth-authorization-server
  • GET /.well-known/oauth-protected-resource
  • GET /.well-known/jwks.json
  • POST /register
  • GET|POST /authorize
  • POST /token
  • POST|GET /mcp protected by Authorization: Bearer <token>

OAuth details:

  • Authorization Code flow with PKCE (S256 required)
  • Dynamic Client Registration for public clients (token_endpoint_auth_method: none)
  • Built-in login form and consent screen using AUTH_USERNAME and AUTH_PASSWORD
  • JWT access tokens signed by a persisted RSA key
  • Rotating refresh tokens for public clients

Persisted OAuth state:

  • signing key
  • registered clients
  • authorization codes
  • refresh tokens

If you want this state to live on the host filesystem, mount a directory such as:

  • /mnt/storage_1/docker/cocktail-recipes-mcp/oauth

Keep OAUTH_STORAGE_DIR=/data/oauth inside the container and set OAUTH_STORAGE_HOST_DIR to the host path above in Portainer or compose.

Local Run

  1. Create env file:
cp .env.example .env
  1. Install dependencies and run:
python3.11 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -e .[test]
python -m cocktail_recipes_mcp.main

Docker Run

Build image:

docker build -t cocktail-recipes-mcp:latest .

Run container:

docker run --rm -i --env-file .env cocktail-recipes-mcp:latest

Compose example:

docker compose up --build cocktail-recipes-mcp

Note: docker-compose.yml reads values from environment variables. For local CLI usage, export them or place them in a local .env file before running docker compose.

For remote clients, set MCP_TRANSPORT=streamable-http, configure PUBLIC_BASE_URL, AUTH_USERNAME, AUTH_PASSWORD, and publish MCP_HTTP_PUBLISH_PORT.

Portainer Deployment (Recommended)

Use this when you run both the cocktail app and this MCP server as containers.

For Portainer "Create stack from repository":

  • Keep all compose YAML in this repository.
  • Do not add manual YAML overrides in the Portainer UI.
  • Set Stack environment variables in the UI using the table below.

Portainer Stack Environment Variables

When both stacks share the same external Docker network (cocktail_net), use the app service name in COCKTAIL_API_BASE_URL.

Key Example value Required Notes
COCKTAIL_API_BASE_URL http://cocktail-app:3000 Yes Use service-name URL on shared Docker network.
COCKTAIL_API_USERNAME admin Yes Cocktail app login username.
COCKTAIL_API_PASSWORD change-me Yes Cocktail app login password.
MCP_TRANSPORT streamable-http Yes (remote Claude) Use stdio only for local process clients.
MCP_HTTP_HOST 0.0.0.0 Yes (remote Claude) Container bind address.
MCP_HTTP_PORT 8000 Yes (remote Claude) Internal container port.
MCP_HTTP_PATH /mcp Yes (remote Claude) MCP endpoint path.
MCP_HTTP_PUBLISH_PORT 8011 Yes (remote Claude) Host-published port.
PUBLIC_BASE_URL https://cocktail-mcp.example.com Yes (remote Claude) Public HTTPS origin used by OAuth metadata.
AUTH_USERNAME connector-admin Yes (remote Claude) Login used on OAuth consent screen. Set anything, then use the same when logging in from Claude
AUTH_PASSWORD change-me Yes (remote Claude) Password for the connector login above. Set anything, then use the same when logging in from Claude
OAUTH_STORAGE_DIR /data/oauth Recommended Keep as shown unless you changed image internals.
OAUTH_STORAGE_HOST_DIR /mnt/storage_1/docker/cocktail-recipes-mcp/oauth Recommended Host path for persisted OAuth keys/tokens.

Then:

  1. Ensure the host path in OAUTH_STORAGE_HOST_DIR exists and is writable by Docker.
  2. Deploy/redeploy the stack in Portainer.
  3. In Claude, add a custom connector pointing to https://<your-fqdn>/mcp.

Portainer Error: PermissionError: [Errno 13] Permission denied: '/data/oauth/jwt_signing_key.pem'

Cause:

  • The container runs as a non-root user (appuser).
  • The bind-mounted host directory from OAUTH_STORAGE_HOST_DIR is not writable by that user.

Fix on the Docker host:

  1. Create the directory if needed:
sudo mkdir -p /mnt/storage_1/docker/cocktail-recipes-mcp/oauth
  1. Confirm container UID/GID for appuser:
docker run --rm --entrypoint sh cocktail-recipes-mcp:latest -c 'id -u appuser; id -g appuser'
  1. Set ownership using that output. Example if UID is 100 and GID is 101:
sudo chown -R 100:101 /mnt/storage_1/docker/cocktail-recipes-mcp/oauth
sudo chmod -R u+rwX,g+rwX /mnt/storage_1/docker/cocktail-recipes-mcp/oauth
  1. Redeploy the stack.

Quick test fallback (not for long-term use):

sudo chmod -R 777 /mnt/storage_1/docker/cocktail-recipes-mcp/oauth

Portainer Error: .env not found

If you saw an error like env file /data/compose/<id>/.env not found, the stack was expecting a physical .env file inside Portainer's compose directory.

This repo now avoids that requirement by reading COCKTAIL_API_* from stack environment variables directly.

In Portainer Stack deployment:

  • Add these variables in the Stack Environment variables UI:
    • COCKTAIL_API_BASE_URL
    • COCKTAIL_API_USERNAME
    • COCKTAIL_API_PASSWORD
    • PUBLIC_BASE_URL
    • AUTH_USERNAME
    • AUTH_PASSWORD
    • OAUTH_STORAGE_HOST_DIR
  • Redeploy the stack.

Your example values are valid for this setup.

Portainer Error: pull access denied for cocktail-recipes-mcp

This happens when Portainer tries to pull cocktail-recipes-mcp:latest from a registry. This project is intended to build from source in the stack, not pull from Docker Hub.

Resolution:

  • Use the current compose file from this repo (it uses build and pull_policy: never).
  • In Portainer stack deployment, disable any "pull latest image" behavior for this stack.
  • Redeploy the stack so the image is built locally on the Docker host.

FQDN vs Internal URL

  • If MCP and cocktail app are in the same Docker network, prefer internal URL (http://service-name:port).
  • If MCP is outside that network, use a reachable external URL/FQDN (for example https://cocktails.example.com).

For MCP client access:

  • Same LAN/VPN: http://<host-ip>:<published-port><MCP_HTTP_PATH>
  • Internet-facing: put SWAG/Nginx in front and use https://<fqdn><MCP_HTTP_PATH>

SWAG Nginx Config (Redacted Template)

Use this as a template for SWAG when proxying to an MCP host IP and published port. Replace placeholder values with your own environment values.

# /config/nginx/site-confs/cocktail-mcp.conf

server {
  listen 443 ssl http2;
  server_name mcp.example.com;

  include /config/nginx/ssl.conf;
  include /config/nginx/proxy.conf;

  client_max_body_size 0;

  add_header X-Content-Type-Options "nosniff" always;
  add_header X-Frame-Options "DENY" always;
  add_header Referrer-Policy "no-referrer" always;

  # SWAG not on shared Docker network: use MCP host IP + published port.
  set $mcp_upstream http://192.168.1.50:8011;

  # OAuth discovery endpoints + JWKS
  location ~ ^/\.well-known/(oauth-authorization-server|oauth-protected-resource|jwks\.json)$ {
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Authorization $http_authorization;
    proxy_buffering off;
    proxy_request_buffering off;
    proxy_read_timeout 3600;
    proxy_send_timeout 3600;
    proxy_pass $mcp_upstream;
  }

  # MCP and OAuth flow endpoints
  location ~ ^/(mcp|register|authorize|token|oauth/login|oauth/consent)$ {
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Authorization $http_authorization;
    proxy_buffering off;
    proxy_request_buffering off;
    proxy_read_timeout 3600;
    proxy_send_timeout 3600;
    proxy_pass $mcp_upstream;
  }

  location = /healthz {
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass $mcp_upstream/healthz;
  }

  location / {
    return 404;
  }
}

Expected quick checks:

  • GET /.well-known/oauth-authorization-server returns 200
  • GET /.well-known/oauth-protected-resource returns 200
  • GET /mcp without token returns 401

Go-Live Checklist

  1. MCP and app containers share a network.
  2. MCP env vars are set in Portainer, including PUBLIC_BASE_URL, AUTH_USERNAME, and AUTH_PASSWORD.
  3. Login endpoint is reachable from MCP (POST /api/auth/login).
  4. OAuth state host path is mounted to /data/oauth.
  5. GET /.well-known/oauth-authorization-server succeeds.
  6. GET /.well-known/oauth-protected-resource succeeds.
  7. Unauthenticated GET /mcp returns 401 with WWW-Authenticate pointing at protected-resource metadata.
  8. For admin operations, run dry_run=true first and apply only with dry_run=false.

Claude Desktop Connection

Add an MCP server entry to your Claude Desktop config.

Local Python command

{
  "mcpServers": {
    "cocktail-recipes": {
      "command": "/absolute/path/to/cocktail-recipes-mcp/.venv/bin/python",
      "args": ["-m", "cocktail_recipes_mcp.main"],
      "env": {
        "COCKTAIL_API_BASE_URL": "http://localhost:3000",
        "COCKTAIL_API_USERNAME": "admin",
        "COCKTAIL_API_PASSWORD": "change-me"
      }
    }
  }
}

Docker command

{
  "mcpServers": {
    "cocktail-recipes": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "--env-file",
        "/absolute/path/to/cocktail-recipes-mcp/.env",
        "cocktail-recipes-mcp:latest"
      ]
    }
  }
}

Remote HTTP MCP (different machine)

When running in Portainer with MCP_TRANSPORT=streamable-http, use your reachable URL:

  • Direct host/port example: http://10.0.30.51:8000/mcp
  • SWAG/FQDN example: https://mcp.example.com/mcp

For Claude custom connector setup:

  • URL: https://mcp.example.com/mcp
  • Leave OAuth Client ID empty.
  • Leave OAuth Client Secret empty.
  • Claude should dynamically register as a public client and complete OAuth against this server.

Do not put COCKTAIL_API_USERNAME or COCKTAIL_API_PASSWORD into Claude. Those remain server-side only.

Tests

Run minimal tests:

pytest

Manual Verification

Discovery endpoints:

curl -i https://cocktail-mcp.example.com/.well-known/oauth-authorization-server
curl -i https://cocktail-mcp.example.com/.well-known/oauth-protected-resource
curl -i https://cocktail-mcp.example.com/.well-known/jwks.json

Protected MCP endpoint without a token:

curl -i https://cocktail-mcp.example.com/mcp

Expected result:

  • 401 Unauthorized
  • WWW-Authenticate: Bearer ... resource_metadata="https://cocktail-mcp.example.com/.well-known/oauth-protected-resource"

Automated tests now cover:

  • metadata discovery
  • dynamic client registration
  • authorization code flow with PKCE
  • refresh token rotation
  • /mcp bearer protection

Example Transcript

See docs/example_transcripts.md for a dry_run/apply merge example (Cucumber vs cucumber).

from github.com/Aephir/cocktail-recipes-mcp

Установка Cocktail Recipes

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/Aephir/cocktail-recipes-mcp

FAQ

Cocktail Recipes MCP бесплатный?

Да, Cocktail Recipes MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Cocktail Recipes?

Нет, Cocktail Recipes работает без API-ключей и переменных окружения.

Cocktail Recipes — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Cocktail Recipes в Claude Desktop, Claude Code или Cursor?

Открой Cocktail Recipes на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Cocktail Recipes with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории development