MCP server for SkinGuide: search skincare products by Baumann skin type, product category, budget, and ingredients. Exposes AI-friendly endpoints for dry, oily, sensitive, and all other skin types.
| Website | skinguide.beauty |
| GitHub | robertozerbini/skinguide-mcp-server |
| Protocol | Model Context Protocol 2024-11-05 |
| Transports | stdio (npx) |
- Overview
- Tools
- Repository Structure
- Quick Start
- Environment Variables
- Tool Reference and Examples
- Baumann Skin Type System
- AI Agent Integration
- Direct API Usage (Python)
- Google Colab Notebook
- Example Clients
- API Discovery
- Contributing
- License
SkinGuide MCP Server implements the Model Context Protocol to expose skincare product search and skin type information as structured AI tools.
AI agents, LLM applications, and developer tools can:
- Discover all 16 Baumann skin types and their characteristics
- Search a curated skincare product catalogue filtered by skin type, category, budget, ingredients, and keywords
- Recommend personalised routines based on Baumann axes (O/D, S/R, P/N, W/T)
| Tool | Description |
|---|---|
search_products |
Search products by skin type, brand, category, budget, keyword, and ingredient |
get_skin_type_info |
Detailed info for a Baumann skin type code |
list_skin_types |
All 16 Baumann codes with names, categories, and descriptions |
get_product_types |
All available product categories |
get_routine |
Step-by-step skincare routine for a Baumann skin type (AM/PM, gender) |
get_brands |
All available brands in the database |
get_test_questions |
All Baumann skin type quiz questions (grouped by dimension) |
submit_test_answers |
Submit quiz answers and compute the user's 4-letter skin type code |
get_product_reviews |
User reviews for a product — rating, comment, reviewer skin type, date |
get_product_ingredients |
Full enriched ingredient list for a product (by product_id) |
get_ingredient_info |
Detailed info for a cosmetic ingredient (comedogenicity, irritancy, expert take) |
compare_products |
Top-N products of a category side-by-side, sorted by rating or price |
build_routine |
Full routine for a skin type with product candidates per step and cost summary |
skinguide-mcp-server/
├── Dockerfile # Optional containerized runtime
├── src/
│ ├── index.ts # MCP stdio entry-point (TypeScript)
│ ├── tools.ts # Tool handlers + live API proxy
│ └── types.ts # Shared interfaces/types
├── dist/ # Compiled output (generated)
├── package.json
├── tsconfig.json
├── examples/
│ ├── blogcreation.ipynb # Colab notebook: autonomous blog-writing agent
│ ├── direct_api_client.py
│ ├── node_client.js # Node.js demo (built-in fetch)
│ └── python_client.py # Python MCP stdio demo
└── .well-known/
└── mcp.json # MCP discovery document
npm install
npm startNo installation required. The MCP server runs on-demand via npx and fetches live product data from https://skinguide.beauty/api/products.
docker build -t skinguide-mcp .
docker run -i --rm skinguide-mcpEdit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"skinguide": {
"command": "npx",
"args": ["skinguide-mcp-server"]
}
}
}Edit .cursor/mcp.json:
{
"mcpServers": {
"skinguide": {
"command": "npx",
"args": ["skinguide-mcp-server"]
}
}
}Edit .vscode/mcp.json:
{
"servers": {
"skinguide": {
"type": "stdio",
"command": "node",
"args": ["dist/index.js"]
}
}
}curl https://skinguide.beauty/.well-known/mcp.json| Variable | Default | Description |
|---|---|---|
LIVE_API_URL |
https://skinguide.beauty/api |
Base URL for the live SkinGuide API |
All tools are invoked by the MCP client (Claude, Cursor, VS Code) over stdio. The JSON-RPC 2.0 envelope used internally:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "<tool_name>",
"arguments": {}
}
}List all 16 Baumann skin types. Input: none.
Response excerpt:
{
"skinTypes": [
{
"code": "DSPW",
"name": "Dry, Sensitive, Pigmented, Wrinkled",
"category": "dry-sensitive",
"difficulty": 5,
"description": "The most complex skin type. Needs rich hydration, anti-inflammatory care, brightening, and anti-aging support simultaneously."
},
{
"code": "DSNW",
"name": "Dry, Sensitive, Non-pigmented, Wrinkled",
"category": "dry-sensitive",
"difficulty": 4,
"description": "Dry and sensitive with visible aging but even tone. Prioritise deep hydration, barrier repair, and anti-aging actives."
}
],
"total": 16
}(14 more types omitted — all 16 returned in the real response)
List all available product categories. Input: none.
{
"productTypes": [
{ "id": "Acne Treatment" },
{ "id": "Anti-age Product" },
{ "id": "Anti-inflammatory Product" },
{ "id": "Antioxidant Serum" },
{ "id": "Cleanser" },
{ "id": "Eye Cream" },
{ "id": "Facial Water" },
{ "id": "Foundation" },
{ "id": "Mask" },
{ "id": "Moisturizer" },
{ "id": "Moisturizer Night" },
{ "id": "Oil-control Product" },
{ "id": "Scrub" },
{ "id": "Self-tanning" },
{ "id": "Skin Lightener" },
{ "id": "Sunscreen" }
],
"total": 16
}Input: skinType (string, required) — 4-letter Baumann code, e.g. OSPT, DRNT, DSPW.
Response:
{
"code": "OSPT",
"name": "Oily, Sensitive, Pigmented, Tight",
"category": "oily-sensitive",
"difficulty": 4,
"description": "Very common in teens and young adults. Oily, acne-prone, and sensitive with post-inflammatory dark spots."
}Tip: If
total=0, retry with fewer filters. Ifingredientreturns 0, trykeyword=<ingredient_name>instead — many products have incomplete ingredient data.skinTypemust be a 4-letter Baumann code (e.g.OSPT), not natural language.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
type |
string | Product category — one of the 16 values returned by get_product_types |
skinType |
string | 4-letter Baumann code, e.g. OSPT. Do NOT use natural language like "oily". |
od |
"O" or "D" | Oily or Dry axis |
sr |
"S" or "R" | Sensitive or Resistant axis |
pn |
"P" or "N" | Pigmented or Non-pigmented axis |
wt |
"W" or "T" | Wrinkled or Tight axis |
brand |
string | Filter by brand name (partial match, case-insensitive). E.g. CeraVe, La Roche. |
keyword |
string | Search keyword matched against product name and tags. Use as fallback when ingredient returns 0. |
ingredient |
string | Exact text match against the stored ingredient list. Returns 0 when data is incomplete — use keyword instead. |
budget |
number | Maximum price in dollars (use 5, 10, 20, 50, 100, or 101 for over $100) |
country |
"US" or "AE" | Country market filter. Defaults to US. Aliases: UAE→AE, USA→US. |
limit |
integer | Max results, 1–50 (default 50) |
When ingredient is used, the response includes available_without_ingredient_filter — the number of products that matched all other filters before the ingredient check. If that value is > 0 but total is 0, retry with keyword instead.
Example — oily + sensitive skin (od=O, sr=S), budget $30:
{ "od": "O", "sr": "S", "budget": 30, "limit": 5 }Example — search by skin type with ingredient fallback to keyword:
{ "skinType": "OSPT", "keyword": "niacinamide", "limit": 10 }Response:
{
"total": 5,
"query": { "type": "all", "skinType": null, "od": "O", "sr": "S", "pn": null, "wt": null, "budget": 30, "keyword": null, "ingredient": null },
"products": [
{
"id": 534,
"name": "Persa-Gel 10 Oil-Free Acne Spot Treatment",
"brand": "Clean & Clear",
"type": "Treatment",
"price": 5.97,
"currency": "$",
"vegan": false,
"ingredients": ["Benzoyl Peroxide 10%"],
"image": "https://m.media-amazon.com/images/I/612wW4IikOL._AC_UL320_.jpg",
"link": "https://amzn.to/4aTFj3m",
"country": "US",
"skinTypes": ["OSPT"]
}
]
}Get the step-by-step skincare routine for a Baumann skin type.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
skinType |
string, required | 4-letter Baumann code, e.g. OSPT |
gender |
"male" or "female" | Optional — filter steps by gender |
timeOfDay |
"AM" or "PM" | Optional — filter to morning or evening steps |
Example — AM routine for OSPT:
{ "skinType": "OSPT", "timeOfDay": "AM" }Response:
{
"skinType": "OSPT",
"total": 4,
"steps": [
{ "step": 1, "productType": "Cleanser", "action": "Cleanse", "timeOfDay": "AM" },
{ "step": 2, "productType": "Toner", "action": "Tone", "timeOfDay": "AM" },
{ "step": 3, "productType": "Serum", "action": "Apply serum", "timeOfDay": "AM" },
{ "step": 4, "productType": "Sunscreen", "action": "Apply sunscreen", "timeOfDay": "AM" }
]
}Get all available skincare brands in the database. Input: none.
Response:
{
"total": 42,
"brands": [
"Acure",
"Biossance",
"CeraVe",
"Clean & Clear",
"La Roche-Posay",
"Neutrogena"
]
}Get all Baumann skin type quiz questions. Input: none.
Returns questions grouped by skin dimension (O_D, S_R, P_N, W_T), each with answer options and numeric scores. Use this to guide a user through the skin type assessment before calling submit_test_answers.
Response excerpt:
{
"questions": [
{
"id": "0",
"dimension": "O_D",
"question": "How does your skin feel 2–3 hours after cleansing with no products applied?",
"options": [
{ "text": "Tight and dry", "value": 1 },
{ "text": "Comfortable", "value": 2 },
{ "text": "Slightly shiny", "value": 3 },
{ "text": "Very oily and shiny", "value": 4 }
]
}
],
"total": 24
}Submit answers for the Baumann skin type test and receive the computed 4-letter skin type code.
Requires a Firebase ID token passed via the
SKINGUIDE_AUTH_TOKENenvironment variable (orAuthorization: Bearer <token>in the request). The result is saved to the user's profile.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
answers |
array | One entry per question: { "questionId": "0", "value": 3 } |
questionId is the zero-based string index returned by get_test_questions. value is the numeric score of the selected option.
Example:
{
"answers": [
{ "questionId": "0", "value": 3 },
{ "questionId": "1", "value": 2 }
]
}Response:
{
"skinType": "OSPT",
"scores": { "O_D": 3.2, "S_R": 2.8, "P_N": 3.5, "W_T": 1.1 },
"acne": true,
"darkSpots": false
}Get user reviews for a specific product.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
product_id |
string, required | The product ID as returned by search_products |
sort |
"recent" or "rating" | Sort order — newest first (default) or highest rated first |
limit |
number | Max reviews to return (default 50, max 100) |
Example:
{ "product_id": "534", "sort": "rating", "limit": 10 }Response:
{
"product_id": "534",
"total": 2,
"reviews": [
{
"rating": 5,
"comment": "Cleared my skin in two weeks.",
"skinType": "OSPT",
"date": "2024-11-03"
},
{
"rating": 4,
"comment": "Effective but a bit drying.",
"skinType": "DRNT",
"date": "2024-10-15"
}
]
}Get the full enriched ingredient list for a specific product.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
product_id |
string, required | The product ID as returned by search_products |
Example:
{ "product_id": "534" }Response:
{
"product_id": "534",
"total": 3,
"ingredients": [
{
"name": "Benzoyl Peroxide",
"detail": {
"slug": "benzoyl-peroxide",
"name": "Benzoyl Peroxide",
"description": "An antimicrobial agent that kills acne-causing bacteria.",
"comedogenicity": 0,
"irritancy": 3,
"whatItDoes": ["Antimicrobial", "Keratolytic"],
"expertTake": "Highly effective for inflammatory acne but can be drying."
}
}
]
}Look up detailed information about a cosmetic ingredient. Pass slug (kebab-case) or name (label text) — at least one is required.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
slug |
string | Kebab-case slug, e.g. "niacinamide" or "hyaluronic-acid" |
name |
string | Label name, e.g. "Niacinamide" or "Hyaluronic Acid" |
Example:
{ "slug": "niacinamide" }Response:
{
"found": true,
"ingredient": {
"slug": "niacinamide",
"name": "Niacinamide",
"description": "A form of vitamin B3 that improves skin barrier function and reduces pigmentation.",
"comedogenicity": 0,
"irritancy": 0,
"whatItDoes": ["Brightening", "Barrier repair", "Pore minimising"],
"expertTake": "One of the most versatile and well-tolerated actives for most skin types."
}
}Return the top-N products of a given category structured for side-by-side comparison.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
type |
string, required | Product category (same enum as search_products) |
skinType |
string | 4-letter Baumann code to filter the pool |
country |
"US" or "AE" | Country market. Defaults to US. |
budget |
number | Maximum price per product |
sortBy |
"rating" or "price" | Sort order. Defaults to rating. |
limit |
integer 2–5 | Number of products to return. Defaults to 3. |
Example:
{ "type": "Cleanser", "skinType": "OSPT", "country": "US", "limit": 3 }Response:
{
"type": "Cleanser",
"skinType": "OSPT",
"country": "US",
"sortBy": "rating",
"total": 3,
"products": [
{ "id": 12, "name": "CeraVe Foaming Cleanser", "brand": "CeraVe", "price": 14.99, ... },
{ "id": 87, "name": "La Roche-Posay Toleriane", "brand": "La Roche-Posay", "price": 18.5, ... },
{ "id": 34, "name": "Neutrogena Oil-Free Wash", "brand": "Neutrogena", "price": 8.97, ... }
]
}Build a complete skincare routine for a Baumann skin type with ranked product candidates per step and a cost summary.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
skinType |
string, required | 4-letter Baumann code |
budget |
number | Budget cap (interpretation depends on budgetMode) |
budgetMode |
"per-product" or "total" | per-product (default) caps each step independently; total allocates greedily across all steps |
country |
"US" or "AE" | Country market. Defaults to US. |
sortBy |
"rating" or "price" | Candidate sort within each step. Defaults to rating. |
gender |
"male" or "female" | Restrict to gendered steps. Omit for all. |
timeOfDay |
"AM" or "PM" | Restrict to morning or evening steps. Omit for both. |
limit |
integer 1–5 | Candidate products per step. Defaults to 3. |
Example — AM routine for OSPT, max $30 per product:
{ "skinType": "OSPT", "timeOfDay": "AM", "budget": 30, "budgetMode": "per-product", "limit": 2 }Response:
{
"skinType": "OSPT",
"country": "US",
"budgetMode": "per-product",
"budgetCap": 30,
"totalSteps": 4,
"estimatedMinCost": 38.93,
"steps": [
{
"step": 1,
"productType": "Cleanser",
"action": "Cleanse",
"timeOfDay": "AM",
"lowestPrice": 8.97,
"candidates": [
{ "id": 34, "name": "Neutrogena Oil-Free Wash", "price": 8.97, ... },
{ "id": 12, "name": "CeraVe Foaming Cleanser", "price": 14.99, ... }
]
}
]
}
⚠️ This tool is available in the server but is not exposed in the current MCP discovery document. It can be re-enabled by adding it back to.well-known/mcp.jsonandsrc/index.ts.
Get illustration image URL(s) for a Baumann skin type. Returns portrait images (hosted on Firebase Storage) representing the skin type across different ethnicities.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
skinType |
string, required | 4-letter Baumann code, e.g. OSPT |
race |
"Asian", "Black", "Latin", or "White" | Optional — filter to a specific ethnicity. Omit for all four. |
The Baumann Skin Type Indicator (BSTI) classifies skin along four binary axes:
| Axis | Options | Meaning |
|---|---|---|
| O/D | O = Oily, D = Dry | Sebum production |
| S/R | S = Sensitive, R = Resistant | Skin reactivity |
| P/N | P = Pigmented, N = Non-pigmented | Pigmentation tendency |
| W/T | W = Wrinkled, T = Tight | Aging / wrinkle tendency |
Combined: 16 unique types (DRNT = easiest, DSPW = most complex).
| Code | Name | Category | Difficulty |
|---|---|---|---|
| DRNT | Dry, Resistant, Non-pigmented, Tight | dry-resistant | 1 |
| DRNW | Dry, Resistant, Non-pigmented, Wrinkled | dry-resistant | 2 |
| DRPT | Dry, Resistant, Pigmented, Tight | dry-resistant | 2 |
| DRPW | Dry, Resistant, Pigmented, Wrinkled | dry-resistant | 3 |
| DSNT | Dry, Sensitive, Non-pigmented, Tight | dry-sensitive | 2 |
| DSNW | Dry, Sensitive, Non-pigmented, Wrinkled | dry-sensitive | 4 |
| DSPT | Dry, Sensitive, Pigmented, Tight | dry-sensitive | 3 |
| DSPW | Dry, Sensitive, Pigmented, Wrinkled | dry-sensitive | 5 |
| ORNT | Oily, Resistant, Non-pigmented, Tight | oily-resistant | 1 |
| ORNW | Oily, Resistant, Non-pigmented, Wrinkled | oily-resistant | 2 |
| ORPT | Oily, Resistant, Pigmented, Tight | oily-resistant | 2 |
| ORPW | Oily, Resistant, Pigmented, Wrinkled | oily-resistant | 3 |
| OSNT | Oily, Sensitive, Non-pigmented, Tight | oily-sensitive | 3 |
| OSNW | Oily, Sensitive, Non-pigmented, Wrinkled | oily-sensitive | 4 |
| OSPT | Oily, Sensitive, Pigmented, Tight | oily-sensitive | 4 |
| OSPW | Oily, Sensitive, Pigmented, Wrinkled | oily-sensitive | 5 |
The MCP server communicates over stdio and works with any AI client that supports MCP. Configure it as shown in the Quick Start section above.
For a deeper look at the JSON-RPC 2.0 request/response format used over the wire, see the demo clients in the examples/ directory.
A Jupyter notebook demonstrating an AI agent that writes blog posts using SkinGuide:
examples/blogcreation.ipynb
Features:
- Runs natively in Google Colab
- Uses LangChain + LangGraph to create an autonomous agent
- Bridges MCP server stdio with OpenAI GPT-4o for real-time product research
- Generates Markdown blog posts with real skincare recommendations
Usage:
- Open the notebook in Google Colab
- Add your
OPENAI_API_KEYto Colab Secrets - Clone/mount the repo and run cells sequentially
- Modify the
user_topicvariable to generate custom blog posts
You can also call the live API directly without the MCP server. The API accepts POST requests with JSON payload:
import requests
import json
url = "https://skinguide.beauty/api/products"
payload = {
"type": "Moisturizer Day",
"od": "D", # Dry
"sr": "S", # Sensitive
"budget": 20,
"country": "US",
"limit": 20
}
response = requests.post(url, json=payload)
print("Status Code:", response.status_code)
print("Response:", json.dumps(response.json(), indent=2))Supported parameters:
| Parameter | Type | Description |
|---|---|---|
type |
string | Product category (see get_product_types for full list) |
country |
"US" or "UAE" |
Country for product availability |
od |
"O" or "D" |
Oily or Dry |
sr |
"S" or "R" |
Sensitive or Resistant |
pn |
"P" or "N" |
Pigmented or Non-pigmented |
wt |
"W" or "T" |
Wrinkled or Tight |
budget |
number | Maximum price in dollars (5, 10, 20, 50, 100, or 101 for over $100) |
limit |
number | Maximum number of results (default 50) |
Three demo clients are included in the examples/ directory:
# Direct API client (stdlib only — no Node.js required, works in Colab)
python3 examples/direct_api_client.py
# MCP stdio clients (require Node.js + npm install)
node examples/node_client.js
python3 examples/python_client.pyGET https://skinguide.beauty/.well-known/mcp.json
The document describes server name, version, URL, transport options, and all tools with full JSON Schema input/output definitions — ready for AI agent auto-discovery.
- Fork the repo
- Create a feature branch:
git checkout -b feat/my-feature - Commit:
git commit -m 'feat: add my feature' - Push and open a PR
Please follow Conventional Commits.
MIT — © SkinGuide · skinguide.beauty