内容
# skinguide-mcp-server
> **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.**
[](https://nodejs.org/)
[](https://modelcontextprotocol.io/)
[](LICENSE)
[](https://skinguide.beauty/)
| | |
|---|---|
| Website | [skinguide.beauty](https://skinguide.beauty/) |
| GitHub | [robertozerbini/skinguide-mcp-server](https://github.com/robertozerbini/skinguide-mcp-server) |
| Protocol | [Model Context Protocol 2024-11-05](https://modelcontextprotocol.io/) |
| Transports | stdio (npx) |
---
## Table of Contents
- [Overview](#overview)
- [Tools](#tools)
- [Repository Structure](#repository-structure)
- [Quick Start](#quick-start)
- [Environment Variables](#environment-variables)
- [Tool Reference and Examples](#tool-reference-and-examples)
- [list_skin_types](#list_skin_types)
- [get_product_types](#get_product_types)
- [get_skin_type_info](#get_skin_type_info)
- [search_products](#search_products)
- [get_routine](#get_routine)
- [get_brands](#get_brands)
- [get_test_questions](#get_test_questions)
- [submit_test_answers](#submit_test_answers)
- [get_product_reviews](#get_product_reviews)
- [get_product_ingredients](#get_product_ingredients)
- [get_ingredient_info](#get_ingredient_info)
- [compare_products](#compare_products)
- [build_routine](#build_routine)
- [Baumann Skin Type System](#baumann-skin-type-system)
- [AI Agent Integration](#ai-agent-integration)
- [Direct API Usage (Python)](#direct-api-usage-python)
- [Google Colab Notebook](#google-colab-notebook)
- [Example Clients](#example-clients)
- [API Discovery](#api-discovery)
- [Contributing](#contributing)
- [License](#license)
---
## Overview
SkinGuide MCP Server implements the [Model Context Protocol](https://modelcontextprotocol.io/) 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)
---
## Tools
| 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 |
---
## Repository Structure
```
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
```
---
## Quick Start
### Run locally (recommended for development)
```bash
npm install
npm start
```
### Run via npx (published package)
No installation required. The MCP server runs on-demand via `npx` and fetches live product data from `https://skinguide.beauty/api/products`.
### Run with Docker
```bash
docker build -t skinguide-mcp .
docker run -i --rm skinguide-mcp
```
### Connect to Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"skinguide": {
"command": "npx",
"args": ["skinguide-mcp-server"]
}
}
}
```
### Connect to Cursor
Edit `.cursor/mcp.json`:
```json
{
"mcpServers": {
"skinguide": {
"command": "npx",
"args": ["skinguide-mcp-server"]
}
}
}
```
### Connect via VS Code with GitHub Copilot
Edit `.vscode/mcp.json`:
```json
{
"servers": {
"skinguide": {
"type": "stdio",
"command": "node",
"args": ["dist/index.js"]
}
}
}
```
### Verify
```bash
curl https://skinguide.beauty/.well-known/mcp.json
```
---
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `LIVE_API_URL` | `https://skinguide.beauty/api` | Base URL for the live SkinGuide API |
---
## Tool Reference and Examples
All tools are invoked by the MCP client (Claude, Cursor, VS Code) over stdio. The JSON-RPC 2.0 envelope used internally:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "<tool_name>",
"arguments": {}
}
}
```
### list_skin_types
List all 16 Baumann skin types. Input: none.
Response excerpt:
```json
{
"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)_
### get_product_types
List all available product categories. Input: none.
```json
{
"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
}
```
### get_skin_type_info
Input: `skinType` (string, required) — 4-letter Baumann code, e.g. `OSPT`, `DRNT`, `DSPW`.
Response:
```json
{
"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."
}
```
### search_products
> **Tip:** If `total=0`, retry with fewer filters. If `ingredient` returns 0, try `keyword=<ingredient_name>` instead — many products have incomplete ingredient data. `skinType` must 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:
```json
{ "od": "O", "sr": "S", "budget": 30, "limit": 5 }
```
Example — search by skin type with ingredient fallback to keyword:
```json
{ "skinType": "OSPT", "keyword": "niacinamide", "limit": 10 }
```
Response:
```json
{
"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_routine
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:
```json
{ "skinType": "OSPT", "timeOfDay": "AM" }
```
Response:
```json
{
"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_brands
Get all available skincare brands in the database. Input: none.
Response:
```json
{
"total": 42,
"brands": [
"Acure",
"Biossance",
"CeraVe",
"Clean & Clear",
"La Roche-Posay",
"Neutrogena"
]
}
```
---
### get_test_questions
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:
```json
{
"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_test_answers
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_TOKEN` environment variable (or `Authorization: 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:
```json
{
"answers": [
{ "questionId": "0", "value": 3 },
{ "questionId": "1", "value": 2 }
]
}
```
Response:
```json
{
"skinType": "OSPT",
"scores": { "O_D": 3.2, "S_R": 2.8, "P_N": 3.5, "W_T": 1.1 },
"acne": true,
"darkSpots": false
}
```
---
### get_product_reviews
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:
```json
{ "product_id": "534", "sort": "rating", "limit": 10 }
```
Response:
```json
{
"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_product_ingredients
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:
```json
{ "product_id": "534" }
```
Response:
```json
{
"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."
}
}
]
}
```
---
### get_ingredient_info
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:
```json
{ "slug": "niacinamide" }
```
Response:
```json
{
"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."
}
}
```
---
### compare_products
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:
```json
{ "type": "Cleanser", "skinType": "OSPT", "country": "US", "limit": 3 }
```
Response:
```json
{
"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_routine
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:
```json
{ "skinType": "OSPT", "timeOfDay": "AM", "budget": 30, "budgetMode": "per-product", "limit": 2 }
```
Response:
```json
{
"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, ... }
]
}
]
}
```
---
### get_skin_type_image
> ⚠️ 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.json` and `src/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. |
## Baumann Skin Type System
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).
### All 16 Types
| 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 |
---
## AI Agent Integration
The MCP server communicates over stdio and works with any AI client that supports MCP. Configure it as shown in the [Quick Start](#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.
---
## Google Colab Notebook
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:**
1. Open the notebook in [Google Colab](https://colab.research.google.com/)
2. Add your `OPENAI_API_KEY` to Colab Secrets
3. Clone/mount the repo and run cells sequentially
4. Modify the `user_topic` variable to generate custom blog posts
---
## Direct API Usage (Python)
You can also call the live API directly without the MCP server. The API accepts POST requests with JSON payload:
```python
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) |
---
## Example Clients
Three demo clients are included in the `examples/` directory:
```bash
# 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
```
---
## API Discovery
```
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.
---
## Contributing
1. Fork the repo
2. Create a feature branch: `git checkout -b feat/my-feature`
3. Commit: `git commit -m 'feat: add my feature'`
4. Push and open a PR
Please follow [Conventional Commits](https://www.conventionalcommits.org/).
---
## License
MIT — © SkinGuide · [skinguide.beauty](https://skinguide.beauty/)
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器