This commit is contained in:
Isaac Johnson 2026-01-28 07:10:08 -06:00
commit c5d6ec4a36
12 changed files with 451 additions and 0 deletions

10
.gemini/settings.json.old Normal file
View File

@ -0,0 +1,10 @@
{
"mcpServers": {
"recipeMaker": {
"command": "python",
"args": [
"/home/builder/Workspaces/recipeMakerMCP/server.py"
]
}
}
}

191
.gitignore vendored Normal file
View File

@ -0,0 +1,191 @@
# Created by https://www.toptal.com/developers/gitignore/api/python,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=python,linux
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python,linux

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --default-timeout=100 -r requirements.txt
COPY server.py .
# Ensure the recipes directory exists so permissions can be managed if mounted
RUN mkdir -p recipes/images
ENTRYPOINT ["python", "server.py"]

20
gemini-extension.json Normal file
View File

@ -0,0 +1,20 @@
{
"extensionId": "recipemaker",
"name": "RecipeMaker",
"version": "1.0.1",
"description": "An MCP server for creating recipes with images, running in a Docker container.",
"mcpServers": [
{
"id": "recipe-maker",
"name": "Recipe Maker Server",
"transportType": "stdio",
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"idjohnson/recipemakermcp:latest"
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1,39 @@
# Traditional Rustic Cornbread
**Servings:** 8 servings
## Description
A classic Southern-style cornbread with a golden, crispy crust and a moist, tender crumb. Baked in a cast-iron skillet for that perfect rustic touch.
![Hero Image of Traditional Rustic Cornbread](images/hero.png)
<!--
NANOBANANA HERO PROMPT:
Photorealistic hero shot of Traditional Rustic Cornbread, A classic Southern-style cornbread with a golden, crispy crust and a moist, tender crumb. Baked in a cast-iron skillet for that perfect rustic touch.. Professional food photography, high detailed, appetizing, 4k resolution, soft natural lighting.
-->
## Ingredients
- 2 cups stone-ground yellow cornmeal
- 1 cup all-purpose flour
- 2 teaspoons baking powder
- 1 teaspoon baking soda
- 1 teaspoon salt
- 2 large eggs
- 1 1/2 cups buttermilk
- 1/2 cup unsalted butter, melted
- 2 tablespoons honey
## Steps
1. Preheat oven to 400°F (200°C) and heat a 10-inch cast-iron skillet.
2. Whisk dry ingredients (cornmeal, flour, powders, salt) in a large bowl.
3. Whisk wet ingredients (eggs, buttermilk, honey) in a separate bowl.
4. Combine wet and dry ingredients. Add melted butter. Stir until just mixed.
5. Grease the hot skillet with butter.
6. Pour batter into skillet.
7. Bake for 20-25 minutes until golden brown.
8. Cool slightly before serving.
![Ingredients and Prep for Traditional Rustic Cornbread](images/prep.png)
<!--
NANOBANANA PREP PROMPT:
Photorealistic overhead shot of ingredients for Traditional Rustic Cornbread, including 2 cups stone-ground yellow cornmeal, 1 cup all-purpose flour, 2 teaspoons baking powder, 1 teaspoon baking soda, 1 teaspoon salt. Kitchen workspace with bowls, mixers, and utensils. Clean, organized, bright lighting.
-->

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,32 @@
# Spaghetti Carbonara
**Servings:** 4 people
## Description
A classic Italian pasta dish made with eggs, cheese, pancetta, and pepper.
![Hero Image of Spaghetti Carbonara](images/spaghetti_carbonara_hero.png)
<!--
NANOBANANA HERO PROMPT:
Photorealistic hero shot of Spaghetti Carbonara, A classic Italian pasta dish made with eggs, cheese, pancetta, and pepper.. Professional food photography, high detailed, appetizing, 4k resolution, soft natural lighting.
-->
## Ingredients
- 1 lb Spaghetti
- 4 Large Eggs
- 1 cup Pecorino Romano
- 0.5 lb Pancetta
- Black Pepper
## Steps
1. Boil water
2. Cook pasta
3. Fry pancetta
4. Mix eggs and cheese
5. Combine all
![Ingredients and Prep for Spaghetti Carbonara](images/spaghetti_carbonara_prep.png)
<!--
NANOBANANA PREP PROMPT:
Photorealistic overhead shot of ingredients for Spaghetti Carbonara, including 1 lb Spaghetti, 4 Large Eggs, 1 cup Pecorino Romano, 0.5 lb Pancetta, Black Pepper. Kitchen workspace with bowls, mixers, and utensils. Clean, organized, bright lighting.
-->

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
mcp[cli]
Pillow

144
server.py Normal file
View File

@ -0,0 +1,144 @@
from mcp.server.fastmcp import FastMCP
from PIL import Image, ImageDraw, ImageFont
import os
import textwrap
# Initialize the FastMCP server
mcp = FastMCP("recipe-maker")
# Constants
RECIPE_DIR = "recipes"
IMAGE_DIR = "recipes/images"
# Ensure directories exist
os.makedirs(RECIPE_DIR, exist_ok=True)
os.makedirs(IMAGE_DIR, exist_ok=True)
def create_placeholder(text: str, file_path: str, color: str = "lightgray"):
"""Creates a simple placeholder image with text."""
width, height = 1024, 1024
img = Image.new('RGB', (width, height), color=color)
d = ImageDraw.Draw(img)
# Simple text centering logic
try:
# Try to load a font, otherwise fallback to default
font = ImageFont.load_default()
# Scale up text if possible (rudimentary)
# In a real app we'd load a TTF
except:
font = None
text_lines = textwrap.wrap(text, width=40)
y_text = height // 2 - (len(text_lines) * 15)
for line in text_lines:
d.text((width // 2, y_text), line, fill="black", anchor="mm", font=font)
y_text += 30
img.save(file_path)
def generate_nanobanana_hero_prompt(title: str, description: str) -> str:
return (
f"Photorealistic hero shot of {title}, {description}. "
"Professional food photography, high detailed, appetizing, 4k resolution, soft natural lighting."
)
def generate_nanobanana_prep_prompt(title: str, ingredients: list[str]) -> str:
ing_list = ", ".join(ingredients[:5]) # Top 5 ingredients
return (
f"Photorealistic overhead shot of ingredients for {title}, including {ing_list}. "
"Kitchen workspace with bowls, mixers, and utensils. Clean, organized, bright lighting."
)
@mcp.tool()
def create_recipe(title: str, description: str, ingredients: list[str], steps: list[str], servings: str) -> str:
"""
Creates a recipe as a Markdown file with placeholder images.
Args:
title: The title of the recipe.
description: A brief description.
ingredients: List of ingredient strings (e.g. "1 cup flour").
steps: List of cooking steps.
servings: Serving size information.
"""
safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '_')).replace(' ', '_').lower()
recipe_filename = os.path.join(RECIPE_DIR, f"{safe_title}.md")
hero_img_name = f"{safe_title}_hero.png"
prep_img_name = f"{safe_title}_prep.png"
hero_img_path = os.path.join(IMAGE_DIR, hero_img_name)
prep_img_path = os.path.join(IMAGE_DIR, prep_img_name)
# Generate prompts
hero_prompt = generate_nanobanana_hero_prompt(title, description)
prep_prompt = generate_nanobanana_prep_prompt(title, ingredients)
# Create placeholders
create_placeholder(f"HERO: {title}\nPrompt: {hero_prompt}", hero_img_path, color="lightblue")
create_placeholder(f"PREP: {title}\nPrompt: {prep_prompt}", prep_img_path, color="lightgreen")
# Create Markdown content
md_content = f"""# {title}
**Servings:** {servings}
## Description
{description}
![Hero Image of {title}](images/{hero_img_name})
<!--
NANOBANANA HERO PROMPT:
{hero_prompt}
-->
## Ingredients
"""
for ing in ingredients:
md_content += f"- {ing}\n"
md_content += "\n## Steps\n"
for i, step in enumerate(steps, 1):
md_content += f"{i}. {step}\n"
md_content += f"""
![Ingredients and Prep for {title}](images/{prep_img_name})
<!--
NANOBANANA PREP PROMPT:
{prep_prompt}
-->
"""
with open(recipe_filename, "w") as f:
f.write(md_content)
return f"Recipe created at {recipe_filename}"
@mcp.tool()
def create_hero_image(title: str, description: str) -> str:
"""Generates a hero image placeholder and prompt."""
safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '_')).replace(' ', '_').lower()
img_name = f"{safe_title}_hero_custom.png"
img_path = os.path.join(IMAGE_DIR, img_name)
prompt = generate_nanobanana_hero_prompt(title, description)
create_placeholder(f"HERO: {title}\nPrompt: {prompt}", img_path, color="lightblue")
return f"Created placeholder at {img_path}. Prompt: {prompt}"
@mcp.tool()
def create_prep_image(title: str, ingredients: list[str]) -> str:
"""Generates a prep image placeholder and prompt."""
safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '_')).replace(' ', '_').lower()
img_name = f"{safe_title}_prep_custom.png"
img_path = os.path.join(IMAGE_DIR, img_name)
prompt = generate_nanobanana_prep_prompt(title, ingredients)
create_placeholder(f"PREP: {title}\nPrompt: {prompt}", img_path, color="lightgreen")
return f"Created placeholder at {img_path}. Prompt: {prompt}"
if __name__ == "__main__":
mcp.run()