尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证MCP server for SkinGuide: search skincare products by Baumann skin type, product category, country, and budget. Exposes AI-friendly endpoints for dry, sensitive, and other skin types
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗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 from GitHub, or local node) |
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:
| 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 | Ingredient list (INCI names) for a product (by product_id) |
get_ingredient_info | Structured facts for a cosmetic ingredient (functions, comedogenicity, irritancy), credited to Incidecoder |
compare_products | Top-N products of a category side-by-side (cheapest first by default, or by rating) |
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 start
The package is not published on npm; npx installs it straight from this GitHub repository. The prepare script compiles TypeScript to dist/ during install, so nothing needs to be built by hand. The server fetches live data from https://skinguide.beauty/api.
npx -y github:robertozerbini/skinguide-mcp-server#v1.2.0
The package has a single executable (skinguide), so npx runs it without a --package flag. The first run takes a little longer while it installs and compiles.
docker build -t skinguide-mcp .
docker run -i --rm skinguide-mcp
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"skinguide": {
"command": "npx",
"args": ["-y", "github:robertozerbini/skinguide-mcp-server#v1.2.0"]
}
}
}
Edit .cursor/mcp.json:
{
"mcpServers": {
"skinguide": {
"command": "npx",
"args": ["-y", "github:robertozerbini/skinguide-mcp-server#v1.2.0"]
}
}
}
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. |
sort | "price-asc", "price-desc" or "rating-desc" | Optional. Omit it for the fast default order (lowest price first). rating-desc is slow (about 10s or more) and only a few hundred products have ratings |
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 is the number of matching products across the whole catalog; returned is how many are in this response (at most limit). All filters are applied by the SkinGuide API.
{
"total": 5,
"returned": 1,
"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",
"title": "...",
"url": "https://skinguide.beauty/routine/OSPT",
"routineDescription": "...",
"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 the skincare brands in the catalog. The full list has several thousand entries, so pass the optional query (case-insensitive substring) to narrow it.
| Parameter | Type | Description |
|---|---|---|
query | string | Optional — e.g. "cera" |
Response (unfiltered):
{
"total": 5986,
"brands": [
"Acure",
"Biossance",
"CeraVe",
"Clean & Clear",
"La Roche-Posay",
"Neutrogena"
]
}
Get all Baumann skin type quiz questions. Input: none.
Returns the 15 questions, each tagged with its skin dimension (section: O_D, S_R, P_N, W_T) and answer options with numeric scores. Source: GET https://skinguide.beauty/api/testQuestions.
Response excerpt:
{
"questions": [
{
"id": "0",
"section": "O_D",
"text": "How does your skin generally feel and look throughout the day (including after cleansing, without moisturizer, facial shine, and pore visibility)?",
"options": [
{ "text": "Very Dry: Feels very dry, flaky, or cracks without moisturizer; ...", "score": 8 },
{ "text": "Balanced: Feels tight but not flaky, ...", "score": 16 }
]
}
],
"total": 15,
"sections": ["O_D", "S_R", "P_N", "W_T"],
"sectionLabels": { "O_D": "Oily vs. Dry", "S_R": "Sensitive vs. Resistant", "P_N": "Pigmented vs. Non-Pigmented", "W_T": "Wrinkled vs. Tight" }
}
Submit answers for the Baumann skin type test and receive the computed 4-letter skin type code.
Requires a signed-in SkinGuide account. The live endpoint (
POST https://skinguide.beauty/api/submitTestAnswers) returns 401 without a Firebase ID token and saves the result to that user's profile. The server reads the token from theSKINGUIDE_AUTH_TOKENenvironment variable. Without it the tool returns an error that sends the user to skinguide.beauty to take the test. The score-to-type thresholds are stored server-side, so the tool does not compute the type locally.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
answers | array | One entry per question: { "questionId": "0", "value": 3 } |
Send all 15 answers. questionId is the id returned by get_test_questions, and value is the 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. Source: the productReviews Cloud Function (https://us-central1-vanity-e971f.cloudfunctions.net/productReviews?product_id=<id>), because reviews are not behind the skinguide.beauty/api routes.
sort=ratingcurrently fails upstream (HTTP 500). When that happens the tool fetches reviews newest-first, sorts them by rating locally, and adds a_notesaying so.
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": "0be8e8e29fe881af821bed6979799c32", "limit": 10 }
Response:
{
"product_id": "0be8e8e29fe881af821bed6979799c32",
"product_name": null,
"avg_rating": null,
"review_count": null,
"total": 1,
"reviews": [
{
"reviewId": "0be8e8e29fe881af821bed6979799c32_...",
"rating": 5,
"comment": "I have dry, slightly sensitive skin, and this Hydro Jelly Fruit Mask was such a pleasant surprise...",
"userSkinType": "DSNW",
"userAgeRange": "35–44",
"createdAt": "2026-05-05T03:27:45.446Z"
}
]
}
Get the full ingredient list for a specific product. Source: the productIngredients Cloud Function (…cloudfunctions.net/productIngredients?product_id=<id>).
At the time of writing the live function returns ingredient names only. If an entry ever carries a
detailobject, the server passes through only the same structured fields asget_ingredient_info(never prose) and adds a_data_credit. Useget_ingredient_infofor an ingredient's functions and ratings.
Input parameters:
| Parameter | Type | Description |
|---|---|---|
product_id | string, required | The product ID as returned by search_products |
Example:
{ "product_id": "f839d81fabc8bfd2662226c7579738a7" }
Response (excerpt):
{
"product_id": "f839d81fabc8bfd2662226c7579738a7",
"total": 74,
"ingredients": [
{ "name": "Aqua" }
]
}
Look up detailed information about a cosmetic ingredient. Pass slug (kebab-case) or name (label text); at least one is required. Source: the ingredientInfo Cloud Function (…cloudfunctions.net/ingredientInfo?name=<name> or ?slug=<slug>). An unknown ingredient returns found: false.
Data credit. Only structured fields are returned: functions, comedogenicity, irritancy, the editorial rating (
our_take), and also-called names. They come from Incidecoder (now INKEEDecoder), and_data_creditsays so. The catalog's prosedescriptionandquick_factsare Incidecoder's text and are deliberately not returned. This follows skinguide.beauty's own rule: "Functions and safety ratings sourced from Incidecoder." When you present these fields, credit Incidecoder with thesource_url.
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": {
"name": "Niacinamide",
"slug": "niacinamide",
"also_called": "vitamin B3, nicotinamide",
"what_it_does": "cell-communicating ingredient, skin brightening, anti-acne, ...",
"all_functions": "smoothing",
"comedogenicity": null,
"irritancy": null,
"our_take": "superstar",
"source_url": "https://incidecoder.com/ingredients/niacinamide"
},
"_data_credit": "Functions and safety ratings sourced from Incidecoder (now INKEEDecoder): https://incidecoder.com/ingredients/niacinamide. Ingredient descriptions are not included.",
"_source": "skinguide.beauty",
"_note": "Ingredient data is served by skinguide.beauty, but these fields come from Incidecoder (INKEEDecoder). ..."
}
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" | Optional. Omit it for the fast default (lowest price first). rating is slow (about 10s) and few products are rated. |
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": "default",
"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. Omit it for the fast default (lowest price first). rating is slow (about 10s per step). |
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.
An agent skill that teaches AI agents when and how to use these tools (the source of the Agensi listing) lives in agensi-skill/.
A Jupyter notebook demonstrating an AI agent that writes blog posts using SkinGuide:
examples/blogcreation.ipynb
Features:
Usage:
OPENAI_API_KEY to Colab Secretsuser_topic variable to generate custom blog postsYou 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.py
GET 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.
git checkout -b feat/my-featuregit commit -m 'feat: add my feature'Please follow Conventional Commits.
MIT — © SkinGuide · skinguide.beauty