first
This commit is contained in:
commit
c5d6ec4a36
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"recipeMaker": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"/home/builder/Workspaces/recipeMakerMCP/server.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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 |
|
|
@ -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.
|
||||
|
||||

|
||||
<!--
|
||||
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.
|
||||
|
||||

|
||||
<!--
|
||||
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 |
|
|
@ -0,0 +1,32 @@
|
|||
# Spaghetti Carbonara
|
||||
|
||||
**Servings:** 4 people
|
||||
|
||||
## Description
|
||||
A classic Italian pasta dish made with eggs, cheese, pancetta, and pepper.
|
||||
|
||||

|
||||
<!--
|
||||
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
|
||||
|
||||

|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
mcp[cli]
|
||||
Pillow
|
||||
|
|
@ -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}
|
||||
|
||||

|
||||
<!--
|
||||
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"""
|
||||

|
||||
<!--
|
||||
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()
|
||||
Loading…
Reference in New Issue