A Model Context Protocol (MCP) server that allows ChatGPT to persistently store, retrieve, and organize recipe files. Recipes are stored as Markdown files with YAML frontmatter, making them both AI-friendly and human-readable.
- Persistent Storage: Recipes stored as Markdown files with YAML frontmatter
- Three Core Tools:
save_recipe: Create or update recipes with metadataread_recipe: Retrieve recipe contentlist_recipes: List all recipes with optional category filtering
- Security: URL-based authentication via obscure UUID path
- Deployment: Docker-based deployment on Render with persistent disk storage
The server uses:
- Protocol: Model Context Protocol (MCP) over HTTP + Server-Sent Events (SSE)
- Storage: Markdown files with YAML frontmatter in
/app/recipes - Authentication: Security by obscurity - UUID embedded in URL path
- Framework: FastAPI with SSE support
recipe-mcp-server/
├── server.py # Main MCP server implementation
├── Dockerfile # Docker configuration
├── requirements.txt # Python dependencies
├── .dockerignore # Docker ignore patterns
├── .gitignore # Git ignore patterns
├── README.md # This file
└── recipes/ # Local testing directory (not in Docker)
└── .gitkeep
-
Clone the repository (or navigate to the project directory)
-
Create a virtual environment:
python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
-
Install dependencies:
pip install -r requirements.txt
-
Set environment variables (optional):
export MCP_AUTH_UUID="your-uuid-here" # Optional: set custom UUID export PORT=8000 # Optional: set custom port
-
Run the server:
python server.py
The server will start on
http://localhost:8000
-
Build the Docker image:
docker build -t recipe-mcp-server . -
Run the container:
docker run -p 8000:8000 -v $(pwd)/recipes:/app/recipes recipe-mcp-serverNote: For local testing, you may want to mount a local recipes directory.
- Render account
- GitHub repository (optional, for automatic deployments)
-
Push code to GitHub (if using GitHub integration)
-
Create a Web Service on Render:
- Go to Render Dashboard
- Click "New +" → "Web Service"
- Connect your GitHub repository (or use public Git repository)
-
Configure the service:
- Name:
recipe-mcp-server(or your preferred name) - Runtime:
Docker - Instance Type:
Starter(or any tier that supports Disks) - Build Command: (leave empty, Docker handles this)
- Start Command: (leave empty, Dockerfile CMD handles this)
- Name:
-
Add Persistent Disk (CRITICAL):
- In the service settings, go to "Disks"
- Click "Add Disk"
- Mount Path:
/app/recipes - Size:
1GB(adjust as needed) - Name:
recipe-storage
-
Set Environment Variables (optional):
MCP_AUTH_UUID: Custom UUID for authentication path (if not set, one will be generated)PORT: Port number (default: 8000, Render sets this automatically)
-
Deploy:
- Click "Create Web Service"
- Wait for deployment to complete
-
Get your MCP endpoint URL:
- After deployment, visit the service URL
- The root endpoint (
/) will show the MCP endpoint path - Your full MCP URL will be:
https://your-service.onrender.com/mcp-{uuid}/sse
-
Get your MCP endpoint URL from the Render service (see above)
-
Open ChatGPT Developer Settings:
- Go to ChatGPT settings
- Navigate to Developer Mode or MCP settings
- Add a new MCP server
-
Configure the MCP server:
- Name: Recipe Manager (or your preferred name)
- URL:
https://your-service.onrender.com/mcp-{uuid}/sse - Type: HTTP + SSE
-
Save and test:
- The server should now be available to ChatGPT
- Try asking ChatGPT to save a recipe to test the connection
Creates or updates a recipe file with YAML frontmatter.
Endpoint: POST /mcp-{uuid}/sse/tools/save_recipe
Request Body:
{
"filename": "spicy_tacos.md",
"content": "# Spicy Tacos\n\n**Ingredients:**\n- ...",
"category": "Dinner",
"tags": ["mexican", "spicy", "easy"],
"prep_time": "15m",
"cook_time": "20m",
"servings": 4
}Optional Fields:
prep_time: Preparation time (e.g., "15m", "30 minutes")cook_time: Cooking time (e.g., "45m", "1 hour")servings: Number of servings (integer)
Response:
{
"success": true,
"message": "Recipe 'spicy_tacos.md' saved successfully",
"filename": "spicy_tacos.md"
}Retrieves the full content of a recipe file (including frontmatter).
Endpoint: POST /mcp-{uuid}/sse/tools/read_recipe
Request Body:
{
"filename": "spicy_tacos.md"
}Response:
{
"success": true,
"filename": "spicy_tacos.md",
"content": "---\ncategory: Dinner\ntags: [mexican, spicy, easy]\n---\n# Spicy Tacos\n\n..."
}Lists all recipe files, optionally filtered by category.
Endpoint: POST /mcp-{uuid}/sse/tools/list_recipes
Request Body:
{
"category": "Dinner" // Optional
}Response:
{
"success": true,
"recipes": ["spicy_tacos.md", "lasagna.md"],
"count": 2
}Recipes are stored as Markdown files with YAML frontmatter:
---
category: "Dinner"
tags: ["italian", "pasta", "comfort-food"]
prep_time: "30m"
---
# Classic Lasagna
**Ingredients:**
- ...
**Instructions:**
1. ...The server uses "security by obscurity" - the authentication is embedded in the URL path itself:
- A random UUID is generated (or set via
MCP_AUTH_UUIDenvironment variable) - The MCP endpoint is only accessible at
/mcp-{uuid}/sse - If someone doesn't know the UUID, they cannot access the server
Important:
- Do not share your MCP URL publicly
- If the URL is compromised, anyone with the URL can read/write your recipes
- Consider regenerating the UUID if the URL is exposed
The server implements strict filename validation:
- Rejects filenames containing
../or path separators - Rejects absolute paths
- Only allows
.mdfiles - All file operations are constrained to
/app/recipes
- Invalid filenames: Returns 400 Bad Request with error message
- Missing files: Returns 404 Not Found
- Invalid frontmatter: Gracefully handles parsing errors (skips file in list if needed)
- Disk full: Returns 500 Internal Server Error
- Check that port 8000 is available (or set
PORTenvironment variable) - Verify all dependencies are installed:
pip install -r requirements.txt - Check Docker logs if running in Docker
- On Render: Verify the persistent disk is mounted at
/app/recipes - Check disk size and usage in Render dashboard
- Verify the disk is properly attached to the service
- Verify the full URL including the UUID path
- Check that the server is running and accessible
- Test the root endpoint (
/) to get the correct MCP path - Check Render service logs for errors
- Verify recipes have valid YAML frontmatter
- Check that the
categoryfield is set in the frontmatter - Category matching is case-insensitive
Test the server endpoints:
# Start the server
python server.py
# In another terminal, test save_recipe
curl -X POST http://localhost:8000/mcp-{uuid}/sse/tools/save_recipe \
-H "Content-Type: application/json" \
-d '{"filename": "test.md", "content": "# Test Recipe", "category": "Test"}'
# Test read_recipe
curl -X POST http://localhost:8000/mcp-{uuid}/sse/tools/read_recipe \
-H "Content-Type: application/json" \
-d '{"filename": "test.md"}'
# Test list_recipes
curl -X POST http://localhost:8000/mcp-{uuid}/sse/tools/list_recipes \
-H "Content-Type: application/json" \
-d '{}'This project is provided as-is for personal use.
For issues or questions:
- Check the troubleshooting section above
- Review Render service logs
- Verify the MCP endpoint URL is correct